diff --git a/front/src/app/axios/api-types.ts b/front/src/app/axios/api-types.ts index 6c0a2a7..c9a6038 100644 --- a/front/src/app/axios/api-types.ts +++ b/front/src/app/axios/api-types.ts @@ -32,6 +32,13 @@ export type PublicKey = { export type DeployResponse = { title: string; owners: PublicKey[]; - confirmations: number; + confirmations: string; }; export type MultiSigResponse = {}; + +export type InvitationResponse = { + token: string; + token_expired_at: number; + refresh_token: string; + refresh_token_expired_at: number; +}; diff --git a/front/src/app/axios/global.service.ts b/front/src/app/axios/global.service.ts index 1173e37..54c02d8 100644 --- a/front/src/app/axios/global.service.ts +++ b/front/src/app/axios/global.service.ts @@ -1,6 +1,7 @@ import { globalService } from "../axios/global-api"; import { AxiosResponse } from "axios"; import Cookies from "js-cookie"; + import { LoginResponse, OrganizationsResponse, @@ -9,6 +10,7 @@ import { AddPartResponse, DeployResponse, MultiSigResponse, + InvitationResponse, } from "./api-types"; export class AccountingService { async login(seedKey: string): Promise> { @@ -44,10 +46,16 @@ export class AccountingService { } async getEmployees( - organizationId: string + organizationId: string, + ids: string[] ): Promise> { - return await globalService.get( - `organizations/${organizationId}/participants` + return await globalService.post( + `organizations/${organizationId}/participants/fetch`, + { + data: { + ids: [...ids, organizationId], + }, + } ); } @@ -69,16 +77,19 @@ export class AccountingService { // POST /organizations/{organization_id}/multisig async deployMultisig( - organization_id: string, + organizationId: string, title: string, owners: string[], confirmations: number ): Promise> { - return await globalService.post(`${organization_id}/multi-sig`, { - title, - owners, - confirmations, - }); + return await globalService.post( + `organizations/${organizationId}/multisig`, + { + title, + owners, + confirmations, + } + ); } async getAllMultisigsByOrganizationId( @@ -86,6 +97,25 @@ export class AccountingService { ): Promise> { return await globalService.get(`organizations/${organizationId}/multisig`); } + + async sentInvitation( + hash: string, + name: string, + credentals: { + email: string; + phone: string; + telegram: string; + }, + mnemonic: string + ): Promise> { + return await globalService.post(`/invite/${hash}/join`, { + data: { + name, + credentals, + mnemonic, + }, + }); + } } export const apiService = new AccountingService(); diff --git a/front/src/app/login/LoginPage.tsx b/front/src/app/login/LoginPage.tsx index bc73418..e769052 100644 --- a/front/src/app/login/LoginPage.tsx +++ b/front/src/app/login/LoginPage.tsx @@ -14,17 +14,32 @@ export function LoginPage() { const { passwordVisible, setPasswordVisible } = useLoginHooks(); const [inp, setInp] = useState(""); // - const [seed, setSeed] = useState(["airport","donate","language","disagree","dumb","access","insect","tribe","ozone","humor","foot","jealous","much","digital",]); + const [seed, setSeed] = useState([ + "airport", + "donate", + "language", + "disagree", + "dumb", + "access", + "insect", + "tribe", + "ozone", + "humor", + "foot", + "jealous", + "much", + "digital", + ]); // const [seed, setSeed] = useState(["melody","correct","brain","slide","flip","polar","asset","know","pencil","major","smile","vital","nominee","merge","addict"]); const [disabled, setDisabled] = useState(true); const router = useRouter(); const onLogin = async () => { - const result = await apiService.login(seed.join(' ')); + const result = await apiService.login(seed.join(" ")); // if (result) { - Cookies.set("accessToken", result.data.token); - router.push("/organization"); + Cookies.set("accessToken", result.data.token); + router.push("/organization"); // } }; const onRegister = async () => { @@ -33,7 +48,7 @@ export function LoginPage() { Cookies.set("accessToken", result.data.token); router.push("/organization"); } - } + }; // const getSeed = (event: React.ChangeEvent) => { // setSeed(inp); // }; @@ -44,7 +59,7 @@ export function LoginPage() { }, [inp]); const onSubmitHandler = async () => { - setSeed((prevState) => prevState ? [...prevState, inp] : [inp]); + setSeed((prevState) => (prevState ? [...prevState, inp] : [inp])); setInp(""); }; @@ -91,14 +106,17 @@ export function LoginPage() { - {
- {seed && seed.map((element: string, index: number) => ( - - ))} -
} + > + {seed && + seed.map((element: string, index: number) => ( + + ))} + + } diff --git a/front/src/app/orgCreate/OrgCard.tsx b/front/src/app/orgCreate/OrgCard.tsx index 3d062f1..1cec142 100644 --- a/front/src/app/orgCreate/OrgCard.tsx +++ b/front/src/app/orgCreate/OrgCard.tsx @@ -14,7 +14,6 @@ type OrgItemProps = { export const OrganizationCard: FC = ({ element }) => { const router = useRouter(); const id: any = element.id; - return ( <> = ({ element }) => { type="inner" extra={ Go To Overview diff --git a/front/src/app/orgCreate/OrgCreatePage.tsx b/front/src/app/orgCreate/OrgCreatePage.tsx index 776955d..b92a0df 100644 --- a/front/src/app/orgCreate/OrgCreatePage.tsx +++ b/front/src/app/orgCreate/OrgCreatePage.tsx @@ -13,6 +13,7 @@ import { apiService } from "../axios/global.service"; import { OrgForm } from "./OrgForm"; import { OrganizationCard } from "./OrgCard"; import { FolderOpenTwoTone } from "@ant-design/icons"; +import useOrganizationsHooks from "@/hooks/organizations"; import { Organization, NewOrgResponse, @@ -20,7 +21,8 @@ import { } from "../axios/api-types"; import Cookies from "js-cookie"; export function OrgCreatePage() { - const [organizations, setOrganizations] = useState([]); + //const [organizations, setOrganizations] = useState([]); + const { organizations, setOrganizations } = useOrganizationsHooks(); const [isModalOpen, setIsModalOpen] = useState(false); const [formData, setFormData] = useState({ name: "", @@ -47,16 +49,19 @@ export function OrgCreatePage() { setIsModalOpen(false); }; + console.log(organizations); + const handleCancel = () => { setIsModalOpen(false); }; useEffect(() => { loadOrganizations(); }, []); + const loadOrganizations = async () => { const result = await apiService.getOrganizations(); if (result) { - setOrganizations(result.data.items); + setOrganizations(result.data.items || []); } }; return ( @@ -69,7 +74,7 @@ export function OrgCreatePage() {
- {organizations.length === 0 && ( + {organizations.length && ( )} {organizations.length ? ( diff --git a/front/src/app/organization/overview/dashboard/id.tsx b/front/src/app/organization/overview/dashboard/[id].tsx similarity index 61% rename from front/src/app/organization/overview/dashboard/id.tsx rename to front/src/app/organization/overview/dashboard/[id].tsx index 8c361aa..6d8404b 100644 --- a/front/src/app/organization/overview/dashboard/id.tsx +++ b/front/src/app/organization/overview/dashboard/[id].tsx @@ -1,10 +1,17 @@ "use client"; import React, { useState, useEffect } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import { Card } from "antd"; +import { useRouter } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { UserOutlined } from "@ant-design/icons"; import { Button, List, Divider, Typography, Avatar, Skeleton } from "antd"; +import { OrganizationCard } from "@/app/orgCreate/OrgCard"; +import { Organization } from "@/app/axios/api-types"; +import useOrganizationsHooks from "@/hooks/organizations"; +import { apiService } from "@/app/axios/global.service"; +type OrgItemProps = { + element: Organization; +}; interface DataType { gender?: string; name: { @@ -35,53 +42,51 @@ const data = [ const { Title } = Typography; export function OrgProfile() { + const { organizations, setOrganizations, loadOrganizations } = + useOrganizationsHooks(); + const [organization, setOrganization] = useState({ + id: "", + name: "", + address: "", + }); const [initLoading, setInitLoading] = useState(true); const [loading, setLoading] = useState(false); const [dataEmployees, setData] = useState([]); - const [list, setList] = useState([]); + const [list, setList] = useState([]); const router = useRouter(); - const pathname = useSearchParams(); - console.log(pathname.getAll("query")); + const searchParams = useSearchParams(); + useEffect(() => { + const id = searchParams.get("id") || ""; + const filteredOrganization = organizations.find( + (element) => element.id === id + ); + + if (filteredOrganization) { + loadEmployees(id); + setOrganization(filteredOrganization); + setInitLoading(false); + } + }, [organizations]); + + useEffect(() => { + loadOrganizations(); + }, []); + + const loadEmployees = async (id: string) => { + const data: any = await apiService.getEmployees(id, []); + setList(data.data.participants); + }; const onNextPageHandler = () => { router.push("/organization/overview/employees"); }; const onMultisigPageHandler = () => { - router.push("/organization/overview/multiSig"); + router.push("/organization/overview/multiSig/?id=" + organization.id); }; - useEffect(() => { - fetch(fakeDataUrl) - .then((res) => res.json()) - .then((res) => { - setInitLoading(false); - setData(res.results); - setList(res.results); - }); - }, []); const onLoadMore = () => { - setLoading(true); - setList( - dataEmployees.concat( - [...new Array(count)].map(() => ({ - loading: true, - name: {}, - picture: {}, - })) - ) - ); - fetch(fakeDataUrl) - .then((res) => res.json()) - .then((res) => { - const newData = dataEmployees.concat(res.results); - setData(newData); - setList(newData); - setLoading(false); - // Resetting window's offsetTop so as to display react-virtualized demo underfloor. - // In real scene, you can using public method of react-virtualized: - // https://stackoverflow.com/questions/46700726/how-to-use-public-method-updateposition-of-react-virtualized - window.dispatchEvent(new Event("resize")); - }); + loadEmployees(organization.id); + setInitLoading(false); }; const loadMore = !initLoading && !loading ? ( @@ -101,7 +106,12 @@ export function OrgProfile() {
Dashboard - + {organization && } +
+ + {/* Address

Phone

Description

-
+ */}
diff --git a/front/src/app/organization/overview/dashboard/page.tsx b/front/src/app/organization/overview/dashboard/page.tsx index 70c0f2f..1419bef 100644 --- a/front/src/app/organization/overview/dashboard/page.tsx +++ b/front/src/app/organization/overview/dashboard/page.tsx @@ -1,6 +1,7 @@ "use client"; import React from "react"; -import { OrgProfile } from "./id"; +import { OrgProfile } from "./[id]"; +import { useSearchParams } from "next/navigation"; export default function Home() { return ( diff --git a/front/src/app/organization/overview/employees/Employee.tsx b/front/src/app/organization/overview/employees/Employee.tsx index f2bff6a..6c30cc5 100644 --- a/front/src/app/organization/overview/employees/Employee.tsx +++ b/front/src/app/organization/overview/employees/Employee.tsx @@ -6,8 +6,26 @@ import React, { useState, useEffect } from "react"; import { Input, Button, Typography, Space } from "antd"; import { MailOutlined, CopyOutlined } from "@ant-design/icons"; +import { apiService } from "@/app/axios/global.service"; + export function EmployeeInvitatonPage() { const { Text } = Typography; + const [values, setValues] = useState({ + email: "ower@gmail.com", + phone: "79999999999", + telegram: "@ower", + }); + + async function sendInvite() { + const data = await apiService.sentInvitation( + "", + "Alex", + values, + "short orient camp maple lend pole balance token pledge fat analyst badge art happy sadsad" + ); + console.log(data); + } + return (
@@ -16,12 +34,21 @@ export function EmployeeInvitatonPage() {
Invite new Employee - + + setValues((prev) => ({ ...prev, email: e.target.value })) + } + /> diff --git a/front/src/app/organization/overview/employees/employeeList/EmployeeList.tsx b/front/src/app/organization/overview/employees/employeeList/EmployeeList.tsx index 51b4862..fda2d4f 100644 --- a/front/src/app/organization/overview/employees/employeeList/EmployeeList.tsx +++ b/front/src/app/organization/overview/employees/employeeList/EmployeeList.tsx @@ -4,6 +4,10 @@ import { Button, Menu, List, Typography, Avatar, Skeleton } from "antd"; import { UserOutlined } from "@ant-design/icons"; import type { MenuProps } from "antd"; import { PayOutBtn } from "@/app/ui/PayOutBtn"; +import { apiService } from "@/app/axios/global.service"; +import useOrganizationsHooks from "@/hooks/organizations"; +import { useSearchParams } from "next/navigation"; +import { Organization } from "@/app/axios/api-types"; const count = 8; const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat,picture&noinfo`; interface DataType { @@ -25,11 +29,35 @@ interface DataType { const { Title } = Typography; type MenuItem = Required["items"][number]; export function EmployeeList() { + const { + organizations, + filteredOrganization, + setOrganizations, + loadOrganizations, + } = useOrganizationsHooks(); const [collapsed, setCollapsed] = useState(true); const [initLoading, setInitLoading] = useState(true); const [loading, setLoading] = useState(false); const [dataEmployees, setData] = useState([]); const [list, setList] = useState([]); + const [organization, setOrganization] = useState({ + id: "", + name: "", + address: "", + }); + const loadEmployees = async (id: string) => { + const data: any = await apiService.getEmployees(id, []); + setList(data.data.participants); + }; + const searchParams = useSearchParams(); + useEffect(() => { + const id = searchParams.get("id") || ""; + if (filteredOrganization) { + loadEmployees(id); + setOrganization(filteredOrganization); + setInitLoading(false); + } + }, [organizations]); useEffect(() => { fetch(fakeDataUrl) .then((res) => res.json()) @@ -41,27 +69,7 @@ export function EmployeeList() { }, []); const onLoadMore = () => { setLoading(true); - setList( - dataEmployees.concat( - [...new Array(count)].map(() => ({ - loading: true, - name: {}, - picture: {}, - })) - ) - ); - fetch(fakeDataUrl) - .then((res) => res.json()) - .then((res) => { - const newData = dataEmployees.concat(res.results); - setData(newData); - setList(newData); - setLoading(false); - // Resetting window's offsetTop so as to display react-virtualized demo underfloor. - // In real scene, you can using public method of react-virtualized: - // https://stackoverflow.com/questions/46700726/how-to-use-public-method-updateposition-of-react-virtualized - window.dispatchEvent(new Event("resize")); - }); + loadEmployees(organization.id); }; const loadMore = !initLoading && !loading ? ( @@ -77,7 +85,7 @@ export function EmployeeList() {
) : null; return ( -
+
Employee List ( - ]}> - - } />} - title={{item.name?.last}} - description="1Lbcfr7sAHTD9CgdQo3HTMTkV8LK4ZnX71" - /> -
wallet address
-
-
- )} + renderItem={(item) => { + console.log(item); + + return ( + ]}> + + } />} + title={{item.name}} + description="1Lbcfr7sAHTD9CgdQo3HTMTkV8LK4ZnX71" + /> +
wallet address
+
+
+ ); + }} />
); diff --git a/front/src/app/organization/overview/multiSig/MultiSigPage.tsx b/front/src/app/organization/overview/multiSig/MultiSigPage.tsx index cf7be74..64aaeeb 100644 --- a/front/src/app/organization/overview/multiSig/MultiSigPage.tsx +++ b/front/src/app/organization/overview/multiSig/MultiSigPage.tsx @@ -6,14 +6,37 @@ import { Card } from "antd"; import { WalletOutlined } from "@ant-design/icons"; import type { InputNumberProps } from "antd"; import { Col, InputNumber, Row, Slider, Select, Button } from "antd"; - +import { apiService } from "@/app/axios/global.service"; +import Cookies from "js-cookie"; +import { useRouter, useSearchParams } from "next/navigation"; const { Title } = Typography; export function MultisigPage() { + const searchParams = useSearchParams(); + const [multiSigData, setmultiSigData] = useState({ + title: "", + owners: [], + confirmations: 0, + organizationId: searchParams.get("id") || "", + }); + const router = useRouter(); const [inputValue, setInputValue] = useState(1); const onChange: InputNumberProps["onChange"] = (newValue) => { setInputValue(newValue as number); }; + + const onDeployMultisig = async () => { + const result = await apiService.deployMultisig( + multiSigData.organizationId, + multiSigData.title, + multiSigData.owners, + multiSigData.confirmations + ); + if (result) { + //Cookies.set("multisig", {result.data.confirmations, result.data.owners, result.data.organizationId, result.data.title}); + } + }; + return (
@@ -93,7 +116,7 @@ export function MultisigPage() {
-
diff --git a/front/src/hooks/organizations.tsx b/front/src/hooks/organizations.tsx new file mode 100644 index 0000000..5e892a7 --- /dev/null +++ b/front/src/hooks/organizations.tsx @@ -0,0 +1,31 @@ +import { useState } from "react"; +import { Organization } from "@/app/axios/api-types"; +import { apiService } from "@/app/axios/global.service"; +import { useSearchParams } from "next/navigation"; + +export default function useOrganizationsHooks() { + const [organizations, setOrganizations] = useState([]); + const [filteredOrganization, setFilteredOrganization] = useState< + Organization[] + >([]); + const loadOrganizations = async () => { + const result = await apiService.getOrganizations(); + if (result) { + setOrganizations(result.data.items || []); + } + const searchParams = useSearchParams(); + const id = searchParams.get("id") || ""; + const filteredOrg = organizations.find((element) => element.id === id); + + if (filteredOrg) { + setFilteredOrganization(filteredOrg || []); + } + }; + + return { + organizations, + filteredOrganization, + setOrganizations, + loadOrganizations, + }; +}