diff --git a/ui/components/atoms/loader/constants.ts b/ui/components/atoms/loader/constants.ts
new file mode 100644
index 0000000..525bc93
--- /dev/null
+++ b/ui/components/atoms/loader/constants.ts
@@ -0,0 +1,11 @@
+export const colorConfig = {
+ blue: [
+ 'rgba(222, 229, 255, 1)',
+ 'rgba(189, 204, 255, 1)',
+ 'rgba(155, 178, 255, 1)',
+ 'rgba(122, 153, 255, 1)',
+ 'rgba(89, 127, 255, 1)',
+ ],
+};
+
+export const disabledColor = 'rgba(232, 233, 235, 1)';
diff --git a/ui/components/atoms/loader/index.ts b/ui/components/atoms/loader/index.ts
new file mode 100644
index 0000000..9a5222d
--- /dev/null
+++ b/ui/components/atoms/loader/index.ts
@@ -0,0 +1,2 @@
+export { default as Loader } from './loader';
+export { LoaderVariant } from './loader';
diff --git a/ui/components/atoms/loader/loader.gif b/ui/components/atoms/loader/loader.gif
new file mode 100644
index 0000000..4ae031d
Binary files /dev/null and b/ui/components/atoms/loader/loader.gif differ
diff --git a/ui/components/atoms/loader/loader.tsx b/ui/components/atoms/loader/loader.tsx
new file mode 100644
index 0000000..882dd80
--- /dev/null
+++ b/ui/components/atoms/loader/loader.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { colorConfig, disabledColor } from './constants';
+import loaderIcon from './loader.gif';
+import Image from 'next/image';
+
+export enum LoaderVariant {
+ CIRCLE = 'circle',
+ DOTS = 'dots',
+}
+
+type LoaderProps = {
+ label?: string;
+ disabled?: boolean;
+ dotCount?: number;
+ radius?: number;
+ gap?: number;
+ duration?: number;
+ variant?: LoaderVariant;
+};
+
+const Loader = ({
+ label,
+ disabled,
+ dotCount = 5,
+ radius = 3,
+ gap = 8,
+ duration = 2000,
+ variant = LoaderVariant.DOTS,
+}: LoaderProps): JSX.Element => {
+ const r = radius;
+ const g = gap;
+ const colors = colorConfig.blue;
+
+ const getCX = (r, g, i) => r + (r * 2 + g) * i;
+ const width = getCX(r, g, dotCount - 1) + r;
+
+ const getCircles = (count) => {
+ const arr = [];
+
+ for (let i = 0; i < count; i++) {
+ let values = [];
+ for (let index = 0; index < count * 2 - 1; index++) {
+ values.push(index < i || index > count + i - 1 ? 0 : 1);
+ }
+ values = [0, ...values, 0];
+ arr.push(
+
+ {!disabled && (
+
+ )}
+
+ );
+ }
+ return arr;
+ };
+
+ if (variant === LoaderVariant.CIRCLE) {
+ return ;
+ }
+
+ return (
+
+ );
+};
+
+export default Loader;
diff --git a/ui/components/atoms/tableErrorMessage/TableErrorMessage.tsx b/ui/components/atoms/tableErrorMessage/TableErrorMessage.tsx
new file mode 100644
index 0000000..72b262e
--- /dev/null
+++ b/ui/components/atoms/tableErrorMessage/TableErrorMessage.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import noDataIcon from './img/tablePlaceholder.svg';
+
+import styles from './index.module.css';
+import classNames from 'classnames';
+import Image from 'next/image';
+
+const TableErrorMessage = () => {
+ return (
+
+
+
There is no data yet
+
+ );
+};
+export default TableErrorMessage;
diff --git a/ui/components/atoms/tableErrorMessage/img/tablePlaceholder.svg b/ui/components/atoms/tableErrorMessage/img/tablePlaceholder.svg
new file mode 100644
index 0000000..81d8136
--- /dev/null
+++ b/ui/components/atoms/tableErrorMessage/img/tablePlaceholder.svg
@@ -0,0 +1,25 @@
+
diff --git a/ui/components/atoms/tableErrorMessage/index.module.css b/ui/components/atoms/tableErrorMessage/index.module.css
new file mode 100644
index 0000000..087409c
--- /dev/null
+++ b/ui/components/atoms/tableErrorMessage/index.module.css
@@ -0,0 +1,52 @@
+.errorTitle {
+ margin: 16px 0 0px;
+ font-size: 16px;
+}
+
+.errorTitleWithBtn {
+ margin: 16px 0 8px;
+}
+
+.descriptionError {
+ margin-bottom: 20px;
+ color: rgba(0, 0, 0, 0.4);
+ font-size: 14px;
+}
+
+.errorScreen {
+ padding: 48px 0 32px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+}
+
+.button {
+ margin-bottom: 16px;
+ font-size: 14px;
+ color: #597fff;
+ background-color: #eef1ff;
+ border: 1px solid #597fff;
+ border-radius: 7px;
+ padding: 10px 12px;
+ display: flex;
+ align-items: center;
+}
+
+.button:last-child {
+ margin: 0;
+}
+
+.button:hover {
+ background: #e8ecfa;
+ cursor: pointer;
+}
+
+.button:active {
+ background: #d7dffa;
+}
+
+.button svg {
+ margin-right: 4px;
+ fill: #597fff;
+}
diff --git a/ui/components/atoms/tableErrorMessage/index.ts b/ui/components/atoms/tableErrorMessage/index.ts
new file mode 100644
index 0000000..1cbd085
--- /dev/null
+++ b/ui/components/atoms/tableErrorMessage/index.ts
@@ -0,0 +1 @@
+export { default as TableErrorMessage } from './TableErrorMessage';
diff --git a/ui/components/molecules/selectPlate/SelectPlate.module.css b/ui/components/molecules/selectPlate/SelectPlate.module.css
new file mode 100644
index 0000000..f0708fa
--- /dev/null
+++ b/ui/components/molecules/selectPlate/SelectPlate.module.css
@@ -0,0 +1,81 @@
+.selectPlate {
+ width: max-content;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ cursor: pointer;
+ padding: 4px 12px;
+ user-select: none;
+ background-color: #f0f1f5;
+ border: 2px solid transparent;
+ border-radius: 7px;
+ transition: all ease 200ms;
+ }
+
+ .selectPlate * {
+ transition: all ease 200ms;
+ }
+
+ .selectPlate > * {
+ margin-right: 6px;
+ }
+
+ .selectPlate > *:last-child {
+ margin-right: 0;
+ }
+
+ .selectTitle {
+ font-weight: 600;
+ color: rgba(0, 0, 0, 0.8);
+ line-height: 24px;
+ text-transform: capitalize;
+ white-space: nowrap;
+ }
+
+ .selectCount {
+ font-weight: 500;
+ color: rgba(0, 0, 0, 0.8);
+ line-height: 16px;
+ text-align: center;
+ padding: 2px 8px;
+ border-radius: 30px;
+ background-color: #fff;
+ }
+
+ .selectClear {
+ display: flex;
+ align-items: center;
+ transition: all ease 200ms;
+ }
+
+ .selectClear:hover {
+ transform: scale(1.2);
+ }
+
+ .arrow {
+ fill: rgba(0, 0, 0, 0.8);
+ transform: rotateX(180deg);
+ }
+
+ .arrowDisabled {
+ fill: rgba(0, 0, 0, 0.4);
+ }
+
+ .expanded {
+ border: 2px solid #ebebf0;
+ }
+
+ .active {
+ border: 2px solid #e3e8fd;
+ background-color: #eef1ff;
+ }
+
+ .active span {
+ color: #3e66ec;
+ }
+
+ .active svg {
+ fill: #3e66ec;
+ }
+
+
\ No newline at end of file
diff --git a/ui/components/molecules/selectPlate/img/Arrow.svg b/ui/components/molecules/selectPlate/img/Arrow.svg
new file mode 100644
index 0000000..4f0226c
--- /dev/null
+++ b/ui/components/molecules/selectPlate/img/Arrow.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/components/molecules/selectPlate/index.tsx b/ui/components/molecules/selectPlate/index.tsx
new file mode 100644
index 0000000..0edc676
--- /dev/null
+++ b/ui/components/molecules/selectPlate/index.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import style from './SelectPlate.module.css';
+import ArrowIcon from './img/Arrow.svg';
+import classNames from 'classnames';
+import Image from 'next/image';
+
+type SelectPlateProps = {
+ onClick: () => void;
+ expanded: boolean;
+ title: string;
+ disable: boolean;
+};
+
+const SelectPlate = ({ onClick, expanded, title, disable }: SelectPlateProps): JSX.Element => {
+ return (
+
+ {title}
+
+
+ );
+};
+
+export default SelectPlate;
diff --git a/ui/components/molecules/singleSelect/SingleSelect.module.css b/ui/components/molecules/singleSelect/SingleSelect.module.css
new file mode 100644
index 0000000..fb7427f
--- /dev/null
+++ b/ui/components/molecules/singleSelect/SingleSelect.module.css
@@ -0,0 +1,19 @@
+.singleSelect {
+ position: relative;
+}
+
+.dropdown {
+ padding: 0;
+ border-radius: 7px;
+ width: 100%;
+}
+
+.item {
+ padding: 6px 12px;
+ transition: all ease 200ms;
+ cursor: pointer;
+}
+
+.item:hover {
+ background-color: var(--col--gray3);
+}
diff --git a/ui/components/molecules/singleSelect/index.tsx b/ui/components/molecules/singleSelect/index.tsx
new file mode 100644
index 0000000..e4acc98
--- /dev/null
+++ b/ui/components/molecules/singleSelect/index.tsx
@@ -0,0 +1 @@
+export { default as SingleSelect } from './singleSelect';
diff --git a/ui/components/molecules/singleSelect/singleSelect.tsx b/ui/components/molecules/singleSelect/singleSelect.tsx
new file mode 100644
index 0000000..d915629
--- /dev/null
+++ b/ui/components/molecules/singleSelect/singleSelect.tsx
@@ -0,0 +1,81 @@
+import React, { useEffect, useRef, useState } from 'react';
+import style from './SingleSelect.module.css';
+import DropdownWrapper from '../dropdownWrapper/index';
+import classNames from 'classnames';
+import SelectPlate from '../selectPlate';
+import CustomScrollList from '../customScrollList';
+import { LimitOptions } from '../../../comman/types';
+import { useMedia } from '../../../hooks/useMedia';
+import { useHandleClickOutside } from '../../../hooks/useHandleClickOutside';
+
+type SingleSelectProps = {
+ options: LimitOptions;
+ selectTitle?: string;
+ onChange: (value: any) => void;
+ initValue?: number;
+ disable?: boolean;
+ className?: string;
+};
+
+const SingleSelect = ({
+ options,
+ selectTitle = '',
+ onChange,
+ initValue,
+ disable,
+ className,
+}: SingleSelectProps): JSX.Element => {
+ const [selected, setSelected] = useState(initValue);
+ const [expanded, setExpanded] = useState(false);
+ const ref = useRef();
+ const media = useMedia(0);
+
+ const findTextByValue = (val) => {
+ if (!options || (!val && val !== 0)) return null;
+ const res = options?.filter((elem) => Object.values(elem).indexOf(val) > -1);
+ if (res.length > 0) return res[0].text;
+ return null;
+ };
+
+ useEffect(() => {
+ setSelected(initValue);
+ }, [initValue]);
+
+ const clickHandler = (data) => {
+ if (!disable) {
+ setSelected(data);
+ onChange(data);
+ setExpanded(false);
+ }
+ };
+
+ useHandleClickOutside(ref.current, () => (media.greater.sm ? setExpanded(false) : null));
+
+ return (
+
+
setExpanded(!expanded)}
+ expanded={expanded}
+ title={findTextByValue(selected) || selectTitle}
+ disable={disable}
+ />
+ setExpanded(false)} show={expanded}>
+
+ {options?.map((el) => (
+ {
+ clickHandler(el.value);
+ }}
+ >
+ {el.text}
+
+ ))}
+
+
+
+ );
+};
+
+export default SingleSelect;
diff --git a/ui/components/organisms/accountConent/accountContent.tsx b/ui/components/organisms/accountConent/accountContent.tsx
index c530f27..fb3e98f 100644
--- a/ui/components/organisms/accountConent/accountContent.tsx
+++ b/ui/components/organisms/accountConent/accountContent.tsx
@@ -1,14 +1,82 @@
+import { ORDER_BY, SORT_BY } from "../../../comman/types";
+import { useTable } from "../../../hooks/useTable";
import { ConnectWalletButton } from "../../molecules/connectWalletButton";
+import { Table } from "../table";
+import { ScoringConfig } from "./constants";
+import iconMock from "./img/iconMock.svg";
import style from "./index.module.css";
const AccountContent = () => {
+ const mockData = {
+ data: [
+ { id: "Evgeniy-dev.mina", url: iconMock, time: 1707757181674 },
+ { id: "mina.mina", url: iconMock, time: 1707757181674 },
+ { id: "Evgeniy-dev.mina", url: iconMock, time: 1707757181674 },
+ { id: "mina.mina", url: iconMock, time: 1707757181674 },
+ { id: "Evgeniy-dev.mina", url: iconMock, time: 1707757181674 },
+ { id: "Vitality.mina", url: iconMock, time: 1707757181674 },
+ { id: "@@@@@@@.mina", url: iconMock, time: 1707757181674 },
+ ],
+ size: 100,
+ totalPages: 1,
+ pageable: {
+ sort: {
+ sorted: true,
+ unsorted: false,
+ empty: false,
+ },
+ offset: 0,
+ pageNumber: 0,
+ pageSize: 100,
+ paged: true,
+ unpaged: false,
+ },
+ last: false,
+ totalElements: 2,
+ number: 0,
+ sort: {
+ sorted: true,
+ unsorted: false,
+ empty: false,
+ },
+ first: true,
+ numberOfElements: 100,
+ empty: false,
+ };
return (
-
+
{
+ console.log(data);
+ }}
+ onChangeLimit={(data) => {
+ console.log(data);
+ }}
+ onChangeSort={(data) => {
+ console.log(data);
+ }}
+ onChangeOrder={(data) => {
+ console.log(data);
+ }}
+ />
);
};
diff --git a/ui/components/organisms/accountConent/constants.ts b/ui/components/organisms/accountConent/constants.ts
new file mode 100644
index 0000000..7c27127
--- /dev/null
+++ b/ui/components/organisms/accountConent/constants.ts
@@ -0,0 +1,31 @@
+import { SORT_BY } from "../../../comman/types";
+import { TableTemplates } from "../table/templates";
+
+export const ScoringConfig = [
+ {
+ colName: "names",
+ headerText: "Registered Names",
+ columnTemplate: TableTemplates.NAMES,
+ fields: {
+ value: "id",
+ url: "url",
+ },
+ },
+ {
+ colName: "button",
+ columnTemplate: TableTemplates.BUTTON,
+ headerText: "",
+ fields: {
+ value: "Manage",
+ },
+ },
+ {
+ colName: "Created",
+ columnTemplate: TableTemplates.TIME,
+ headerText: "Created",
+ fields: {
+ value: "time",
+ },
+ sortBy: SORT_BY.SCORE,
+ },
+];
diff --git a/ui/components/organisms/accountConent/img/iconMock.svg b/ui/components/organisms/accountConent/img/iconMock.svg
new file mode 100644
index 0000000..1dc8b95
--- /dev/null
+++ b/ui/components/organisms/accountConent/img/iconMock.svg
@@ -0,0 +1,70 @@
+
+
\ No newline at end of file
diff --git a/ui/components/organisms/accountConent/index.module.css b/ui/components/organisms/accountConent/index.module.css
index d6f8e1d..cdd07c3 100644
--- a/ui/components/organisms/accountConent/index.module.css
+++ b/ui/components/organisms/accountConent/index.module.css
@@ -2,12 +2,13 @@
display: flex;
flex-direction: column;
width: 100%;
+ padding: 0 40px;
}
.header {
display: flex;
justify-content: space-between;
- padding: 16px 0 16px 16px;
+ padding: 16px 0 16px 0px;
}
.header > div:first-child {
diff --git a/ui/components/organisms/navigation/navigation.tsx b/ui/components/organisms/navigation/navigation.tsx
index 710c912..1a0a04b 100644
--- a/ui/components/organisms/navigation/navigation.tsx
+++ b/ui/components/organisms/navigation/navigation.tsx
@@ -23,8 +23,12 @@ const Navigation = () => {
[style.active]: pathname.includes(url),
})}
>
-
+
{title}
);
diff --git a/ui/components/organisms/pager/Pager.module.css b/ui/components/organisms/pager/Pager.module.css
new file mode 100644
index 0000000..6891a1b
--- /dev/null
+++ b/ui/components/organisms/pager/Pager.module.css
@@ -0,0 +1,37 @@
+.pager {
+ display: flex;
+}
+.btn {
+ outline: none !important;
+ border: none;
+ background-color: transparent;
+ min-width: 32px;
+ height: 32px;
+ padding: 0 4px;
+ margin-right: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 7px;
+ transition: all ease 200ms;
+ line-height: 16px;
+ user-select: none;
+ color: rgba(0, 0, 0, 0.8);
+ cursor: pointer;
+}
+
+.btn:hover {
+ background-color: #f0f1f5;
+}
+.btn:last-child {
+ margin-right: 0;
+}
+.ellipsis {
+ cursor: default;
+}
+.ellipsis:hover {
+ background-color: transparent;
+}
+.active {
+ background-color: #f0f1f5;
+}
diff --git a/ui/components/organisms/pager/img/ArrowIcon.svg b/ui/components/organisms/pager/img/ArrowIcon.svg
new file mode 100644
index 0000000..60324ba
--- /dev/null
+++ b/ui/components/organisms/pager/img/ArrowIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/components/organisms/pager/index.tsx b/ui/components/organisms/pager/index.tsx
new file mode 100644
index 0000000..04e1177
--- /dev/null
+++ b/ui/components/organisms/pager/index.tsx
@@ -0,0 +1,124 @@
+import classNames from 'classnames';
+import React from 'react';
+import style from './Pager.module.css';
+import ArrowIcon from './img/ArrowIcon.svg';
+import Image from 'next/image';
+import { useMedia } from '../../../hooks/useMedia';
+
+type PagerProps = {
+ page: number;
+ count?: number;
+ onChange: (value: number) => void;
+ pageNeighbours?: number;
+};
+
+const Pager = ({ page, count = 1, onChange, pageNeighbours = 1 }: PagerProps): JSX.Element => {
+ const showPrevButton = page !== 1;
+ const showNextButton = count !== page;
+ const media = useMedia();
+
+ const ELLIPSIS = '...';
+
+ const range = (from, to, step = 1) => {
+ let i = from;
+ const range = [];
+
+ while (i <= to) {
+ range.push(i);
+ i += step;
+ }
+
+ return range;
+ };
+
+ const fetchPageNumbers = () => {
+ const totalNumbers = pageNeighbours * 2 + 3;
+ const totalBlocks = totalNumbers + 2;
+
+ if (count > totalBlocks) {
+ const startPage = Math.max(2, page - pageNeighbours);
+ const endPage = Math.min(count - 1, page + pageNeighbours);
+ let pages = range(startPage, endPage);
+ const hasLeftSpill = startPage > 2;
+ const hasRightSpill = count - endPage > 1;
+ const spillOffset = totalNumbers - (pages.length + 1);
+ switch (true) {
+ case hasLeftSpill && !hasRightSpill: {
+ const extraPages =
+ (page + 1 + '').length > 3 && !media.greater.sm
+ ? range(startPage - spillOffset + 2, startPage - 1)
+ : range(startPage - spillOffset, startPage - 1);
+ pages = [ELLIPSIS, ...extraPages, ...pages];
+ break;
+ }
+ case !hasLeftSpill && hasRightSpill: {
+ const extraPages = range(endPage + 1, endPage + spillOffset);
+ pages = [...pages, ...extraPages, ELLIPSIS];
+ break;
+ }
+
+ case hasLeftSpill && hasRightSpill:
+ default: {
+ (page + 1 + '').length > 3 && !media.greater.sm
+ ? (pages = [ELLIPSIS, pages[1], ELLIPSIS])
+ : (pages = [ELLIPSIS, ...pages, ELLIPSIS]);
+ break;
+ }
+ }
+
+ return [1, ...pages, count];
+ }
+
+ return range(1, count);
+ };
+
+ const changeHandler = (value) => {
+ if (value > count || value < 1) return;
+ onChange(value - 1);
+ };
+
+ return (
+
+ {showPrevButton && (
+
+ )}
+
+ {fetchPageNumbers().map((el, i) => {
+ if (el === ELLIPSIS)
+ return (
+
+ );
+
+ return (
+
+ );
+ })}
+
+ {showNextButton && (
+
+ )}
+
+ );
+};
+
+export default Pager;
diff --git a/ui/components/organisms/pagination/index.module.css b/ui/components/organisms/pagination/index.module.css
new file mode 100644
index 0000000..a5c497c
--- /dev/null
+++ b/ui/components/organisms/pagination/index.module.css
@@ -0,0 +1,76 @@
+.pagination {
+ background-color: #fff;
+ position: sticky;
+ top: 0;
+ z-index: 12;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 0 16px 0;
+ user-select: none;
+}
+
+.showing {
+ line-height: 32px;
+ flex-basis: 25%;
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.8);
+}
+
+.showingMobile {
+ flex-basis: auto;
+}
+
+.select {
+ flex-basis: 25%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+}
+
+.select span {
+ margin-right: 8px;
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.8);
+}
+
+.totalCount {
+ margin-bottom: 4px;
+ line-height: 18px;
+}
+
+.showingLast {
+ color: #9c9ea6;
+ font-size: 12px;
+}
+
+@media (max-width: 992px) {
+ .showingHiddenOnMobile {
+ display: none;
+ }
+}
+
+@media (max-width: 768px) {
+ .pagination {
+ justify-content: center;
+ }
+ .MobileWithShowing {
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ gap: 10px;
+ padding-right: 16px;
+ }
+
+ .select span {
+ display: none;
+ }
+}
+
+@media (max-width: 576px) {
+ .pagination {
+ padding: 12px 0;
+ justify-content: flex-start;
+ padding-left: 16px;
+ }
+}
diff --git a/ui/components/organisms/pagination/index.ts b/ui/components/organisms/pagination/index.ts
new file mode 100644
index 0000000..8bdff8c
--- /dev/null
+++ b/ui/components/organisms/pagination/index.ts
@@ -0,0 +1 @@
+export { default as Table } from './pagination';
diff --git a/ui/components/organisms/pagination/pagination.tsx b/ui/components/organisms/pagination/pagination.tsx
new file mode 100644
index 0000000..743c738
--- /dev/null
+++ b/ui/components/organisms/pagination/pagination.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import style from './index.module.css';
+import { LimitOptions } from '../../../comman/types';
+import Pager from '../pager';
+import { SingleSelect } from '../../molecules/singleSelect';
+
+type PaginationProps = {
+ currentPage: number;
+ pagesCount: number;
+ pageLimit: number;
+ totalElements: number;
+ onChangePage: (value: number | string) => void;
+ onChangeLimit: (value: number) => void;
+ isLoading: boolean;
+ limitOptions?: LimitOptions;
+};
+
+const Pagination = ({
+ currentPage,
+ pageLimit,
+ totalElements,
+ pagesCount,
+ onChangePage,
+ limitOptions,
+ onChangeLimit,
+ isLoading,
+}: PaginationProps): JSX.Element => {
+ const nextOffset = pageLimit * (currentPage + 1);
+ const offset = pageLimit * currentPage;
+
+ return (
+
+
+
+ Showing {offset + 1} - {nextOffset > totalElements ? totalElements : nextOffset} out of{' '}
+ {totalElements ?? '...'}
+
+
+
+
onChangePage(page)} />
+
+
+ Show
+
+
+
+ );
+};
+
+export default Pagination;
diff --git a/ui/components/organisms/table/errorMessage.tsx b/ui/components/organisms/table/errorMessage.tsx
new file mode 100644
index 0000000..58bfce5
--- /dev/null
+++ b/ui/components/organisms/table/errorMessage.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import ClearIcon from './img/clear.svg';
+
+import styles from './Table.module.css';
+import Image from 'next/image';
+
+const ErrorMessage = ({ errorText, onClearStr }) => {
+ return (
+
+
{errorText ?? 'There is no data yet'}
+ {onClearStr && (
+
+ )}
+
+ );
+};
+export default ErrorMessage;
diff --git a/ui/components/organisms/table/img/ChevronIcon.svg b/ui/components/organisms/table/img/ChevronIcon.svg
new file mode 100644
index 0000000..97e4524
--- /dev/null
+++ b/ui/components/organisms/table/img/ChevronIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/components/organisms/table/img/SortIcon.svg b/ui/components/organisms/table/img/SortIcon.svg
new file mode 100644
index 0000000..5491805
--- /dev/null
+++ b/ui/components/organisms/table/img/SortIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/components/organisms/table/img/clear.svg b/ui/components/organisms/table/img/clear.svg
new file mode 100644
index 0000000..65723bc
--- /dev/null
+++ b/ui/components/organisms/table/img/clear.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui/components/organisms/table/index.module.css b/ui/components/organisms/table/index.module.css
new file mode 100644
index 0000000..2dbd884
--- /dev/null
+++ b/ui/components/organisms/table/index.module.css
@@ -0,0 +1,59 @@
+.wrapper {
+ overflow: hidden;
+ overflow-x: scroll;
+}
+
+.wrapper::-webkit-scrollbar {
+ display: none;
+}
+
+.table {
+ overflow: hidden;
+ overflow-x: scroll;
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.table tbody tr:hover {
+ background-color: rgb(250, 250, 252);
+ transition: all 0.3s;
+ min-width: 100px;
+}
+
+.table thead {
+ font-weight: bold;
+ border-top: 0.5px solid var(--col--gray8);
+ padding: 10px 0;
+}
+
+.table thead th {
+ text-align: start;
+ white-space: nowrap;
+ font-size: 12px;
+}
+
+.table th,
+.table td {
+ padding: 15px;
+ text-align: left;
+ border-bottom: 0.5px solid var(--col--gray8);
+}
+
+.loadingScreen {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+}
+
+.headerCellHover {
+ cursor: pointer;
+}
+
+.sortIcon {
+ margin-bottom: -4px;
+}
+
+.timeTemplate {
+ display: flex;
+ flex-direction: column;
+}
\ No newline at end of file
diff --git a/ui/components/organisms/table/index.ts b/ui/components/organisms/table/index.ts
new file mode 100644
index 0000000..1a876cf
--- /dev/null
+++ b/ui/components/organisms/table/index.ts
@@ -0,0 +1 @@
+export { default as Table } from './table';
diff --git a/ui/components/organisms/table/table.tsx b/ui/components/organisms/table/table.tsx
new file mode 100644
index 0000000..b4dc08d
--- /dev/null
+++ b/ui/components/organisms/table/table.tsx
@@ -0,0 +1,109 @@
+import Image from 'next/image';
+import style from './index.module.css';
+import getCell from './templates';
+
+import sortIcon from './img/SortIcon.svg';
+import classNames from 'classnames';
+import { ORDER_BY } from '../../../comman/types';
+import { TableErrorMessage } from '../../atoms/tableErrorMessage';
+import Pagination from '../pagination/pagination';
+import { Loader, LoaderVariant } from '../../atoms/loader';
+
+const Table = ({
+ data,
+ config: configs,
+ isLoading,
+ currentPage,
+ pageLimit,
+ totalElements,
+ pagesCount,
+ sortBy,
+ orderBy,
+ onChangePage,
+ limitOptions,
+ onChangeLimit,
+ onChangeSort,
+ onChangeOrder,
+}) => {
+ const handleSort = (sort?: string): void => {
+ if (!sort) return;
+ if (sort === sortBy) {
+ return onChangeOrder(orderBy === ORDER_BY.DESC ? ORDER_BY.ASC : ORDER_BY.DESC);
+ }
+ onChangeSort(sort);
+ onChangeOrder(undefined);
+ };
+ const showErrorMessage = !isLoading && (!data || data?.data?.length < 1 );
+
+ const renderPagination = () => {
+ return (
+
+ );
+ };
+
+ return (
+ <>
+ {renderPagination()}
+
+
+
+
+ {configs.map(({ headerText, sortBy: configSortBy }) => (
+ handleSort(configSortBy)}
+ className={classNames('t-inter-semi-bold', {
+ [style.headerCellHover]: configSortBy,
+ })}
+ >
+ {headerText}
+ {sortBy === configSortBy && (
+
+ )}
+ |
+ ))}
+
+
+ {!isLoading && !showErrorMessage && (
+
+ {data?.data?.map((item, index) => {
+ return (
+
+ {configs.map((config, index) => {
+ return {getCell({ data: item, config })} | ;
+ })}
+
+ );
+ })}
+
+ )}
+
+
+ {showErrorMessage && }
+
+ {isLoading ? (
+
+
+
+ ) : (
+ <> {renderPagination()}>
+ )}
+ >
+ );
+};
+
+export default Table;
diff --git a/ui/components/organisms/table/templates/accountTemplate/accountTemplate.tsx b/ui/components/organisms/table/templates/accountTemplate/accountTemplate.tsx
new file mode 100644
index 0000000..0760743
--- /dev/null
+++ b/ui/components/organisms/table/templates/accountTemplate/accountTemplate.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import style from '../index.module.css';
+import AccountName from '../../../../organisms/accountName';
+
+const AccountTemplate = ({ data, config }) => {
+ const pk = data[config.fields?.pk];
+ const img = data[config.fields.img];
+ const name = data[config.fields.name];
+ const view = config.view;
+ const isPkArray = Array.isArray(pk);
+ const newImg = isPkArray ? pk[0]?.[config?.fields?.nestedImg] : img;
+ const newName = isPkArray ? pk[0]?.[config?.fields?.nestedName] : name;
+ const noRedirectFromValue = config.fields.noRedirectFromValue;
+ const noRedirectFromData = data[config.fields.noRedirectFromData];
+ const redirect = config.fields.getRedirectFromData
+ ? config.fields.getRedirectFromData(data)
+ : config.fields.redirect || 'account';
+ const noRedirect = noRedirectFromData ?? (noRedirectFromValue ? noRedirectFromValue(pk) : config.fields.noRedirect);
+
+ return (
+
+
config.fields.getRedirectLink(pk, data[config.fields.isValidator])
+ : null
+ }
+ noRedirect={noRedirect}
+ />
+
+ );
+};
+
+export default AccountTemplate;
diff --git a/ui/components/organisms/table/templates/amountTemplate/index.module.css b/ui/components/organisms/table/templates/amountTemplate/index.module.css
new file mode 100644
index 0000000..f48af85
--- /dev/null
+++ b/ui/components/organisms/table/templates/amountTemplate/index.module.css
@@ -0,0 +1,7 @@
+.addionValue {
+ font-size: 10px;
+ color: var(--col--gray4);
+ line-height: 16px;
+ text-transform: uppercase;
+ margin-left: 5px;
+}
diff --git a/ui/components/organisms/table/templates/amountTemplate/index.tsx b/ui/components/organisms/table/templates/amountTemplate/index.tsx
new file mode 100644
index 0000000..0c50ef2
--- /dev/null
+++ b/ui/components/organisms/table/templates/amountTemplate/index.tsx
@@ -0,0 +1,29 @@
+import classNames from 'classnames';
+import styles from './index.module.css';
+import { formatNum } from '../../../../../comman/helpers';
+
+type AmountTemplateProps = {
+ data: any;
+ config: {
+ fields: {
+ value: string;
+ additionValue?: string;
+ };
+ };
+};
+
+const AmountTemplate = ({ data, config }: AmountTemplateProps): JSX.Element => {
+ const value = data[config.fields.value];
+ const additionValue = data[config.fields.additionValue] || config.fields.additionValue;
+
+ return (
+
+ <>
+ {formatNum(value, 2, true, true)}
+ {additionValue && {additionValue}}
+ >
+
+ );
+};
+
+export default AmountTemplate;
diff --git a/ui/components/organisms/table/templates/buttonTemplate/buttonTemplate.tsx b/ui/components/organisms/table/templates/buttonTemplate/buttonTemplate.tsx
new file mode 100644
index 0000000..0f889d7
--- /dev/null
+++ b/ui/components/organisms/table/templates/buttonTemplate/buttonTemplate.tsx
@@ -0,0 +1,18 @@
+import { Button } from "../../../../atoms/button";
+import { Variant } from "../../../../atoms/button/types";
+
+type ButtonTemplateProps = {
+ data: any;
+ config: {
+ fields: {
+ value: string;
+ url: string;
+ };
+ };
+};
+const ButtonTemplate = ({ data, config }: ButtonTemplateProps) => {
+ const buttonText = config.fields.value;
+ return ;
+};
+
+export default ButtonTemplate;
diff --git a/ui/components/organisms/table/templates/cardsTemplate/cardsTemplate.tsx b/ui/components/organisms/table/templates/cardsTemplate/cardsTemplate.tsx
new file mode 100644
index 0000000..b1fba45
--- /dev/null
+++ b/ui/components/organisms/table/templates/cardsTemplate/cardsTemplate.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import classNames from "classnames";
+
+import styles from "../index.module.css";
+
+type CardsTemplateProps = {
+ data: any;
+ config: {
+ fields: {
+ value: string;
+ tooltipFromData?: string;
+ tooltipText?: string;
+ func: (value: string) => void;
+ prefix?: string;
+ postfix?: string;
+ additionValue?: string;
+ };
+ };
+};
+
+const CardsTemplate = ({ data, config }: CardsTemplateProps) => {
+ const value = data[config.fields?.value];
+ const prefix = config.fields?.prefix;
+ const postfix = config.fields?.postfix;
+ const additionValue =
+ data[config.fields?.additionValue] || config.fields?.additionValue;
+ const valFunc = config.fields?.func;
+ const hideValues = ["wrong", "invalid"];
+
+ const isShowDash =
+ (typeof value === "string" &&
+ hideValues.includes(String(value).toLowerCase())) ||
+ value === null;
+
+ return (
+
+ sdfkjsdnjfm
+
+ );
+};
+
+export default CardsTemplate;
diff --git a/ui/components/organisms/table/templates/index.module.css b/ui/components/organisms/table/templates/index.module.css
new file mode 100644
index 0000000..b54b80a
--- /dev/null
+++ b/ui/components/organisms/table/templates/index.module.css
@@ -0,0 +1,50 @@
+.stringTemplate {
+ font-size: 14px;
+ color: var(--col--dark);
+ line-height: 16px;
+}
+
+.stringTemplateAdionValue {
+ font-size: 10px;
+ color: var(--col--gray4);
+ line-height: 16px;
+ text-transform: uppercase;
+ margin-left: 5px;
+}
+
+.accountTemplate {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 3px;
+}
+
+.accountTemplate > span {
+ display: flex;
+ align-items: center;
+}
+
+.accountTemplateIcon svg {
+ width: 10px;
+ height: 10px;
+}
+
+.namesTemplate {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 4px;
+}
+
+.timeTemplate {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+.timeTemplate span:first-child {
+ color: #4b4b4b;
+}
+
+.timeTemplate span:last-child {
+ color: #b8b8b8;
+}
diff --git a/ui/components/organisms/table/templates/index.tsx b/ui/components/organisms/table/templates/index.tsx
new file mode 100644
index 0000000..409c7ae
--- /dev/null
+++ b/ui/components/organisms/table/templates/index.tsx
@@ -0,0 +1,37 @@
+import React from "react";
+import StringTemplate from "./stringTemplate";
+import AccountTemplate from "./accountTemplate/accountTemplate";
+import AmountTemplate from "./amountTemplate";
+import CardsTemplate from "./cardsTemplate/cardsTemplate";
+import NamesTemplate from "./namesTemplate/namesTemplate";
+import ButtonTemplate from "./buttonTemplate/buttonTemplate";
+import TimeTemplate from "./timeTemplate/timeTemplate";
+
+export enum TableTemplates {
+ STRING = "string",
+ ACCOUNT_TEMPLATE = "accountTemplate",
+ AMOUNT = "amount",
+ CARDS = "cards",
+ NAMES = "names",
+ BUTTON = "button",
+ TIME = "time",
+}
+
+const getCell = ({ data, config }): JSX.Element => {
+ switch (config.columnTemplate) {
+ case TableTemplates.STRING:
+ return ;
+ case TableTemplates.AMOUNT:
+ return ;
+ case TableTemplates.CARDS:
+ return ;
+ case TableTemplates.NAMES:
+ return ;
+ case TableTemplates.BUTTON:
+ return ;
+ case TableTemplates.TIME:
+ return ;
+ }
+};
+
+export default getCell;
diff --git a/ui/components/organisms/table/templates/namesTemplate/namesTemplate.tsx b/ui/components/organisms/table/templates/namesTemplate/namesTemplate.tsx
new file mode 100644
index 0000000..c6c5c48
--- /dev/null
+++ b/ui/components/organisms/table/templates/namesTemplate/namesTemplate.tsx
@@ -0,0 +1,28 @@
+import Image from "next/image";
+
+import style from "../index.module.css";
+import classNames from "classnames";
+
+type NamesTemplateProps = {
+ data: any;
+ config: {
+ fields: {
+ value: string;
+ url: string;
+ };
+ };
+};
+
+const NamesTemplate = ({ data, config }: NamesTemplateProps) => {
+ const value = data[config.fields.value];
+ const imgUrl = data[config.fields.url];
+
+ return (
+
+
+ {value}
+
+ );
+};
+
+export default NamesTemplate;
diff --git a/ui/components/organisms/table/templates/stringTemplate/index.tsx b/ui/components/organisms/table/templates/stringTemplate/index.tsx
new file mode 100644
index 0000000..b71c7ad
--- /dev/null
+++ b/ui/components/organisms/table/templates/stringTemplate/index.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import classNames from "classnames";
+
+import styles from "../index.module.css";
+
+type StringTemplateProps = {
+ data: any;
+ config: {
+ fields: {
+ value: string;
+ tooltipFromData?: string;
+ tooltipText?: string;
+ func: (value: string) => void;
+ prefix?: string;
+ postfix?: string;
+ additionValue?: string;
+ };
+ };
+};
+
+const StringTemplate = ({ data, config }: StringTemplateProps) => {
+ const value = data[config.fields?.value];
+ const prefix = config.fields?.prefix;
+ const postfix = config.fields?.postfix;
+ const additionValue =
+ data[config.fields?.additionValue] || config.fields?.additionValue;
+ const valFunc = config.fields?.func;
+ const hideValues = ["wrong", "invalid"];
+
+ const isShowDash =
+ (typeof value === "string" &&
+ hideValues.includes(String(value).toLowerCase())) ||
+ value === null;
+
+ return (
+
+ {isShowDash ? (
+ "-"
+ ) : (
+ <>
+ {prefix}
+ {!valFunc ? value : valFunc(value)}
+ {postfix}
+ {additionValue && (
+
+ {additionValue}
+
+ )}
+ >
+ )}
+
+ );
+};
+
+export default StringTemplate;
diff --git a/ui/components/organisms/table/templates/timeTemplate/timeTemplate.tsx b/ui/components/organisms/table/templates/timeTemplate/timeTemplate.tsx
new file mode 100644
index 0000000..923e845
--- /dev/null
+++ b/ui/components/organisms/table/templates/timeTemplate/timeTemplate.tsx
@@ -0,0 +1,41 @@
+import { useEffect, useState } from "react";
+import {
+ dateTimeFromTimestamp,
+ getTimeFromMilliseconds,
+ getTimeFromMillisecondsDynamic,
+} from "../../../../../helpers/timeHelper";
+
+import style from "../index.module.css";
+import classNames from "classnames";
+
+type TimeTemplateProps = {
+ data: any;
+ config: {
+ fields: {
+ value: string;
+ url: string;
+ };
+ };
+};
+
+const TimeTemplate = ({ data, config }: TimeTemplateProps) => {
+ const [date, setDate] = useState(Date.now());
+
+ const value = data[config.fields.value];
+
+ useEffect(() => {
+ const interval = setInterval(() => setDate(Date.now()), 1000);
+ return () => {
+ clearInterval(interval);
+ };
+ }, []);
+
+ return (
+
+ {getTimeFromMillisecondsDynamic(date - value, false, true)}
+ {dateTimeFromTimestamp(value)}
+
+ );
+};
+
+export default TimeTemplate;
diff --git a/ui/helpers/timeHelper.ts b/ui/helpers/timeHelper.ts
new file mode 100644
index 0000000..ad3d38b
--- /dev/null
+++ b/ui/helpers/timeHelper.ts
@@ -0,0 +1,96 @@
+import dayjs from "dayjs";
+import dayjsPluginUTC from 'dayjs-plugin-utc'
+
+dayjs.extend(dayjsPluginUTC)
+
+export const getTimeFromMillisecondsDynamic = (
+ millis,
+ fullMeasureNames,
+ isAgo
+) => {
+ const SECONDSMS = 1000;
+ const MINUTESMS = SECONDSMS * 60;
+ const HOURSMS = MINUTESMS * 60;
+ const DAYMS = HOURSMS * 24;
+ const YEARMS = DAYMS * 365;
+
+ const yearsSymbol = fullMeasureNames ? "year" : "y";
+ const daysSymbol = fullMeasureNames ? "day" : "d";
+ const hoursSymbol = fullMeasureNames ? "hour" : "h";
+ const minutesSymbol = fullMeasureNames ? "minute" : "m";
+ const secondsSymbol = fullMeasureNames ? "second" : "s";
+ const ago = isAgo ? " ago" : "";
+
+ let years, days, hours, minutes, seconds;
+
+ years = Math.floor(millis / YEARMS);
+ days = Math.floor(millis / DAYMS);
+ hours = Math.floor(millis / HOURSMS);
+ minutes = Math.floor(millis / MINUTESMS);
+ seconds = Math.floor(millis / SECONDSMS);
+
+ if (years >= 1) return years + yearsSymbol + ago;
+
+ if (days >= 1) return days + daysSymbol + ago;
+
+ if (hours >= 1) {
+ let minutesLeft = minutes - hours * 60;
+ return (
+ hours +
+ hoursSymbol +
+ " " +
+ (minutesLeft < 1 ? "" : minutesLeft + minutesSymbol) +
+ ago
+ );
+ }
+
+ if (minutes >= 1) {
+ let secondsLeft = seconds - minutes * 60;
+ return (
+ minutes +
+ minutesSymbol +
+ " " +
+ (secondsLeft < 1 ? "" : secondsLeft + secondsSymbol) +
+ ago
+ );
+ }
+
+ if (seconds >= 1) return seconds + secondsSymbol + ago;
+
+ return "now";
+};
+
+export const getTimeFromMilliseconds = (millis) => {
+ let timestamp = millis;
+ let ago = "";
+
+ if (!timestamp) return;
+
+ if (timestamp < 0) {
+ timestamp = Math.abs(millis);
+ ago = " ago";
+ }
+
+ let days, hours, minutes;
+
+ days = Math.floor(timestamp / 86400000);
+ if (days >= 1) return days + " days" + ago;
+
+ hours = Math.floor(timestamp / 1000 / 60 / 60);
+ if (!Number.isInteger(hours)) {
+ minutes = Math.floor((timestamp - hours * 60 * 60 * 1000) / 1000 / 60);
+ if (hours >= 1) return hours + "h " + minutes + "m" + ago;
+ }
+ if (hours >= 1) return hours + "h" + ago;
+
+ minutes = Math.floor(timestamp / 1000 / 60);
+ if (minutes >= 1) return minutes + "m" + ago;
+
+ return "< " + Math.ceil(minutes) + "m" + ago;
+};
+
+
+export const dateTimeFromTimestamp = (timestamp) => {
+ const date = dayjs(new Date(timestamp)).format('DD.MM.YYYY HH:mm')
+ return date
+}
\ No newline at end of file
diff --git a/ui/hooks/useHandleClickOutside.ts b/ui/hooks/useHandleClickOutside.ts
new file mode 100644
index 0000000..94f4743
--- /dev/null
+++ b/ui/hooks/useHandleClickOutside.ts
@@ -0,0 +1,24 @@
+import { useCallback, useEffect } from 'react';
+
+export const useHandleClickOutside = (ref, callback) => {
+ const handleClickOutside = useCallback(
+ (e) => {
+ if (Array.isArray(ref)) {
+ const trigger = ref.filter((el) => e.composedPath().includes(el)).length < 1;
+ if (trigger) callback();
+ } else {
+ const current = ref;
+ if (!current) return;
+ if (!e.composedPath().includes(current)) {
+ callback();
+ }
+ }
+ },
+ [ref, callback]
+ );
+
+ useEffect(() => {
+ document.addEventListener('click', handleClickOutside, false);
+ return () => document.removeEventListener('click', handleClickOutside, false);
+ }, [ref]);
+};
diff --git a/ui/hooks/useTable.ts b/ui/hooks/useTable.ts
new file mode 100644
index 0000000..32665f1
--- /dev/null
+++ b/ui/hooks/useTable.ts
@@ -0,0 +1,81 @@
+import { useEffect, useState } from 'react';
+
+type UseTableProps = {
+ defaultState: {
+ limit: number;
+ sortBy: string;
+ orderBy: string;
+ };
+};
+
+type UseTableresult = {
+ limit: number;
+ sortBy: string;
+ orderBy: string;
+ page: number;
+ searchStr: string;
+ resetFilter: () => void;
+ isInitState: boolean;
+ isSearchStrEmpty: boolean;
+ actions: {
+ setSortBy: (value: string) => void;
+ setLimit: (value: number) => void;
+ setOrderBy: (value: string) => void;
+ setPage: (value: number) => void;
+ setSearchStr: (value: string) => void;
+ };
+};
+
+export const useTable = ({ defaultState }: UseTableProps): UseTableresult => {
+ let url;
+ if (typeof window !== 'undefined') {
+ url = window?.location.href;
+ }
+ const [page, setPage] = useState(0);
+ const [limit, setLimit] = useState(defaultState.limit);
+ const [sortBy, setSortBy] = useState(defaultState.sortBy);
+ const [orderBy, setOrderBy] = useState(defaultState.orderBy);
+ const [searchStr, setSearchStr] = useState('');
+
+ const isInitState =
+ defaultState.limit === limit && defaultState.sortBy === sortBy && defaultState.orderBy === orderBy;
+
+ const isSearchStrEmpty = searchStr === '';
+
+ useEffect(() => {
+ setPage(0);
+ }, [limit, orderBy, searchStr, sortBy, url]);
+
+ useEffect(() => {
+ setLimit(defaultState?.limit);
+ setSortBy(defaultState?.sortBy);
+ setOrderBy(defaultState?.orderBy);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [url]);
+
+ const resetFilter = () => {
+ setSearchStr('');
+ setLimit(defaultState?.limit);
+ setSortBy(defaultState?.sortBy);
+ setOrderBy(defaultState?.orderBy);
+ setPage(0);
+ };
+
+ return {
+ limit,
+ sortBy,
+ orderBy,
+ page,
+ searchStr,
+ resetFilter,
+ isInitState,
+ isSearchStrEmpty,
+ actions: {
+ setSortBy,
+ setLimit,
+ setOrderBy,
+ setPage,
+ setSearchStr,
+ },
+ };
+};
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 076efd2..0dc657f 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -10,6 +10,8 @@
"dependencies": {
"@reduxjs/toolkit": "^2.1.0",
"classnames": "^2.5.1",
+ "dayjs": "^1.11.10",
+ "dayjs-plugin-utc": "^0.1.2",
"lodash": "^4.17.21",
"next": "14.1.0",
"react": "^18",
@@ -1003,6 +1005,16 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "node_modules/dayjs": {
+ "version": "1.11.10",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+ },
+ "node_modules/dayjs-plugin-utc": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz",
+ "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg=="
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -4828,6 +4840,16 @@
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
"dev": true
},
+ "dayjs": {
+ "version": "1.11.10",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz",
+ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
+ },
+ "dayjs-plugin-utc": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/dayjs-plugin-utc/-/dayjs-plugin-utc-0.1.2.tgz",
+ "integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg=="
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
diff --git a/ui/package.json b/ui/package.json
index 4ddeb66..9c172c2 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -11,6 +11,8 @@
"dependencies": {
"@reduxjs/toolkit": "^2.1.0",
"classnames": "^2.5.1",
+ "dayjs": "^1.11.10",
+ "dayjs-plugin-utc": "^0.1.2",
"lodash": "^4.17.21",
"next": "14.1.0",
"react": "^18",
diff --git a/ui/pages/account/index.tsx b/ui/pages/account/index.tsx
index 76875ac..10b4cf2 100644
--- a/ui/pages/account/index.tsx
+++ b/ui/pages/account/index.tsx
@@ -2,6 +2,8 @@ import classNames from "classnames";
import AccountContent from "../../components/organisms/accountConent/accountContent";
import Navigation from "../../components/organisms/navigation/navigation";
import style from "./index.module.css";
+import { Table } from "../../components/organisms/table";
+import { ScoringConfig } from "../../components/organisms/accountConent/constants";
export default function Account() {
return (