From 0e5c65f0d7dd6c8c5e26b7bad75bc09438240de6 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Wed, 15 Jan 2025 13:45:03 +0100 Subject: [PATCH 01/84] taking out validation from effects file --- src/features/profile-setup/models/effects.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index 426af945..19d3a9a5 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -13,7 +13,6 @@ import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK } from "@/common/c import { socialDbContractClient } from "@/common/contracts/social"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import deepObjectDiff from "@/common/lib/deepObjectDiff"; -import { store } from "@/store"; import getSocialDataFormat from "../utils/getSocialDataFormat"; @@ -27,25 +26,10 @@ const getSocialData = async (accountId: string) => { } }; -export const saveProject = async () => { - const data = store.getState().projectEditor; - - const accountId = data.isDao ? data.daoAddress : data.accountId; - - if (!accountId) { - return { success: false, error: "No accountId provided" }; - } - +export const saveProject = async (data: any, accountId: string) => { // If Dao, get dao policy const daoPolicy = data.isDao ? await getDaoPolicy(accountId) : null; - // Validate DAO Address - const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; - - if (!isDaoAddressValid) { - return { success: false, error: "DAO: Invalid NEAR account Id" }; - } - // Social Data Format const socialData = getSocialDataFormat(data); From 7c6d45e77964e44093faa8c6101b1d4445a28be1 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Wed, 15 Jan 2025 14:55:06 +0100 Subject: [PATCH 02/84] updated design to use react-hook-form instead --- src/features/profile-setup/hooks/forms.ts | 133 +++++++++++++++++++--- 1 file changed, 117 insertions(+), 16 deletions(-) diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 443b96b8..887523f4 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -1,10 +1,12 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { validateNearAddress } from "@wpdas/naxios"; +import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; +import { ZodError } from "zod"; import { useWallet } from "@/entities/_shared/session"; -import { dispatch } from "@/store"; +import { dispatch, store } from "@/store"; import { saveProject } from "../models/effects"; import { addFundingSourceSchema, projectEditorSchema } from "../models/schemas"; @@ -14,32 +16,131 @@ export const useProjectEditorForm = () => { const form = useForm({ resolver: zodResolver(projectEditorSchema), mode: "onChange", + resetOptions: { keepDirtyValues: false }, + defaultValues: { + name: "", + isDao: false, + daoAddress: "", + backgroundImage: "", + profileImage: "", + teamMembers: [], + categories: [], + description: "", + publicGoodReason: "", + smartContracts: [], + fundingSources: [], + githubRepositories: [], + website: "", + twitter: "", + telegram: "", + github: "", + }, }); + // Watch form values for cross-field validation + const values = useWatch(form); + + // Track cross-field validation errors + const [crossFieldErrors, setCrossFieldErrors] = useState>({}); + + // Handle cross-field validation + useEffect(() => { + void projectEditorSchema + .parseAsync(values) + .then(() => setCrossFieldErrors({})) + .catch((error: ZodError) => + setCrossFieldErrors( + error?.issues.reduce((schemaErrors, { code, message, path }) => { + const fieldPath = path.at(0); + return typeof fieldPath === "string" && code === "custom" + ? { ...schemaErrors, [fieldPath]: { message, type: code } } + : schemaErrors; + }, {}), + ), + ); + }, [values]); + const { wallet } = useWallet(); + const [submitting, setSubmitting] = useState(false); const onSubmit: SubmitHandler = useCallback( - async (_) => { - // not using form data, using store data provided by form - if (wallet) { - dispatch.projectEditor.submissionStatus("sending"); - - saveProject().then(async (result) => { - if (result.success) { - console.log("Opening wallet for approval..."); - } else { - dispatch.projectEditor.submissionStatus("pending"); - } - }); + async (formData) => { + if (!wallet) return; + + const data = store.getState().projectEditor; + + const accountId = data.isDao ? data.daoAddress : data.accountId; + + if (!accountId) { + return { success: false, error: "No accountId provided" }; + } + + // Validate DAO Address + const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; + + if (!isDaoAddressValid) { + return { success: false, error: "DAO: Invalid NEAR account Id" }; } + + setSubmitting(true); + + try { + const result = await saveProject(formData, accountId); + + if (result.success) { + console.log("Opening wallet for approval..."); + } else { + dispatch.projectEditor.submissionStatus("pending"); + } + } catch (error) { + console.error(error); + } finally { + setSubmitting(false); + } + + // // not using form data, using store data provided by form + // if (wallet) { + // dispatch.projectEditor.submissionStatus("sending"); + + // const data = store.getState().projectEditor; + + // const accountId = data.isDao ? data.daoAddress : data.accountId; + + // if (!accountId) { + // return { success: false, error: "No accountId provided" }; + // } + + // // Validate DAO Address + // const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; + + // if (!isDaoAddressValid) { + // return { success: false, error: "DAO: Invalid NEAR account Id" }; + // } + + // saveProject(data, accountId).then(async (result) => { + // if (result.success) { + // console.log("Opening wallet for approval..."); + // } else { + // dispatch.projectEditor.submissionStatus("pending"); + // } + // }); + // } }, [wallet], ); return { - form, + form: { + ...form, + formState: { + ...form.formState, + errors: { ...form.formState.errors, ...crossFieldErrors }, + }, + }, + isSubmitting: submitting, errors: form.formState.errors, onSubmit: form.handleSubmit(onSubmit), + values, }; }; From 2efe5c76a3c300d578f2743483d72124c0e76002 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Thu, 16 Jan 2025 10:25:28 +0100 Subject: [PATCH 03/84] Added more refinement to the hook --- src/features/profile-setup/hooks/forms.ts | 180 ++++++++++++---------- 1 file changed, 102 insertions(+), 78 deletions(-) diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 887523f4..11db56a7 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -1,132 +1,111 @@ import { useCallback, useEffect, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; -import { validateNearAddress } from "@wpdas/naxios"; +import { useRouter } from "next/router"; import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; import { ZodError } from "zod"; import { useWallet } from "@/entities/_shared/session"; -import { dispatch, store } from "@/store"; import { saveProject } from "../models/effects"; import { addFundingSourceSchema, projectEditorSchema } from "../models/schemas"; import { AddFundingSourceInputs, ProjectEditorInputs } from "../models/types"; -export const useProjectEditorForm = () => { +// Project Editor Form Hook +export const useProjectEditorForm = (options: { + defaultValues?: Partial; + onSuccess?: () => void; +}) => { + const router = useRouter(); + const { wallet } = useWallet(); + const [submitting, setSubmitting] = useState(false); + + const [crossFieldErrors, setCrossFieldErrors] = useState>({}); + const form = useForm({ resolver: zodResolver(projectEditorSchema), mode: "onChange", - resetOptions: { keepDirtyValues: false }, defaultValues: { name: "", - isDao: false, - daoAddress: "", - backgroundImage: "", - profileImage: "", - teamMembers: [], - categories: [], description: "", publicGoodReason: "", - smartContracts: [], - fundingSources: [], + isDao: false, + categories: [], + teamMembers: [], githubRepositories: [], - website: "", - twitter: "", - telegram: "", - github: "", }, }); - // Watch form values for cross-field validation - const values = useWatch(form); + const values = useWatch({ control: form.control }); - // Track cross-field validation errors - const [crossFieldErrors, setCrossFieldErrors] = useState>({}); - - // Handle cross-field validation + // Cross-field validation useEffect(() => { void projectEditorSchema .parseAsync(values) .then(() => setCrossFieldErrors({})) .catch((error: ZodError) => setCrossFieldErrors( - error?.issues.reduce((schemaErrors, { code, message, path }) => { + error?.issues.reduce((errors, { code, message, path }) => { const fieldPath = path.at(0); return typeof fieldPath === "string" && code === "custom" - ? { ...schemaErrors, [fieldPath]: { message, type: code } } - : schemaErrors; + ? { ...errors, [fieldPath]: { message, type: code } } + : errors; }, {}), ), ); }, [values]); - const { wallet } = useWallet(); - const [submitting, setSubmitting] = useState(false); - - const onSubmit: SubmitHandler = useCallback( - async (formData) => { - if (!wallet) return; - - const data = store.getState().projectEditor; + // Form update handlers + const updateTeamMembers = useCallback( + (members: string[]) => { + form.setValue("teamMembers", members, { shouldValidate: true }); + }, + [form], + ); - const accountId = data.isDao ? data.daoAddress : data.accountId; + const updateCategories = useCallback( + (categories: string[]) => { + form.setValue("categories", categories, { shouldValidate: true }); + }, + [form], + ); - if (!accountId) { - return { success: false, error: "No accountId provided" }; - } + const updateRepositories = useCallback( + (repos: string[]) => { + form.setValue("githubRepositories", repos, { shouldValidate: true }); + }, + [form], + ); - // Validate DAO Address - const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; + const addRepository = useCallback(() => { + const currentRepos = form.getValues("githubRepositories") || []; + form.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true }); + }, [form]); - if (!isDaoAddressValid) { - return { success: false, error: "DAO: Invalid NEAR account Id" }; - } + const onSubmit: SubmitHandler = useCallback( + async (formData) => { + if (!wallet) return; setSubmitting(true); try { - const result = await saveProject(formData, accountId); + // Project saving logic here + const result = await saveProject(formData, wallet?.accountId?.toString() || ""); + + options.onSuccess?.(); if (result.success) { console.log("Opening wallet for approval..."); - } else { - dispatch.projectEditor.submissionStatus("pending"); } + + router.push("/profile"); } catch (error) { console.error(error); } finally { setSubmitting(false); } - - // // not using form data, using store data provided by form - // if (wallet) { - // dispatch.projectEditor.submissionStatus("sending"); - - // const data = store.getState().projectEditor; - - // const accountId = data.isDao ? data.daoAddress : data.accountId; - - // if (!accountId) { - // return { success: false, error: "No accountId provided" }; - // } - - // // Validate DAO Address - // const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; - - // if (!isDaoAddressValid) { - // return { success: false, error: "DAO: Invalid NEAR account Id" }; - // } - - // saveProject(data, accountId).then(async (result) => { - // if (result.success) { - // console.log("Opening wallet for approval..."); - // } else { - // dispatch.projectEditor.submissionStatus("pending"); - // } - // }); - // } }, - [wallet], + [wallet, options, router], ); return { @@ -137,20 +116,65 @@ export const useProjectEditorForm = () => { errors: { ...form.formState.errors, ...crossFieldErrors }, }, }, - isSubmitting: submitting, errors: form.formState.errors, - onSubmit: form.handleSubmit(onSubmit), values, + isSubmitting: submitting, + updateTeamMembers, + updateCategories, + updateRepositories, + addRepository, + onSubmit: form.handleSubmit(onSubmit), + resetForm: form.reset, }; }; -export const useAddFundingSourceForm = () => { +// Funding Source Form Hook +export const useAddFundingSourceForm = (options: { + defaultValues?: Partial; + onSuccess?: () => void; +}) => { const form = useForm({ resolver: zodResolver(addFundingSourceSchema), + mode: "onChange", + defaultValues: { + description: "", + investorName: "", + amountReceived: "", + denomination: "", + date: undefined, + }, }); + const values = useWatch({ control: form.control }); + const [submitting, setSubmitting] = useState(false); + + const onSubmit: SubmitHandler = useCallback( + async (_) => { + setSubmitting(true); + + try { + // Funding source saving logic here + options.onSuccess?.(); + } catch (error) { + console.error(error); + } finally { + setSubmitting(false); + } + }, + [options], + ); + return { - form, + form: { + ...form, + formState: { + ...form.formState, + errors: form.formState.errors, + }, + }, errors: form.formState.errors, + values, + isSubmitting: submitting, + onSubmit: form.handleSubmit(onSubmit), }; }; From 6e367f6dff333d57aabb60e51af933c24cf7a930 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Thu, 16 Jan 2025 10:32:34 +0100 Subject: [PATCH 04/84] taking out redux code and replacing with rhf --- .../components/ProjectEditor.tsx | 187 ++++++++++-------- 1 file changed, 100 insertions(+), 87 deletions(-) diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index db49f652..c252f161 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -7,7 +7,7 @@ import { prop } from "remeda"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { useWallet } from "@/entities/_shared/session"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +// import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; import { rootPathnames } from "@/pathnames"; import { dispatch, useGlobalStoreSelector } from "@/store"; @@ -37,114 +37,126 @@ import { useProjectEditorForm } from "../hooks/forms"; export const ProjectEditor = () => { const router = useRouter(); const { projectId: projectIdPathParam } = router.query; - - const projectId = - typeof projectIdPathParam === "string" ? projectIdPathParam : projectIdPathParam?.at(0); - - const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); const { wallet, isWalletReady } = useWallet(); - const { isAuthenticated } = useSessionReduxStore(); - const { form, errors, onSubmit } = useProjectEditorForm(); - const values = form.watch(); - - const isOwner = projectTemplate.isDao - ? projectId === projectTemplate.daoAddress - : projectId === wallet?.accountId; - - useEffect(() => { - // Set initial focus to name input. - form.setFocus("name"); - }, [form]); - - // Set default values by profile - useEffect(() => form.reset(projectTemplate), [form, projectTemplate]); - - // Set initial name + // const [editContractIndex, setEditContractIndex] = useState(); const [initialNameSet, setInitialNameSet] = useState(false); - useEffect(() => { - if (!initialNameSet && projectTemplate.name) { - form.setValue("name", projectTemplate.name); - form.trigger(); // re-validate - setInitialNameSet(true); - } - }, [initialNameSet, projectTemplate.name, form]); - - // Store description, public good reason and daoAddress - useEffect(() => { - if (values.name) { - dispatch.projectEditor.setProjectName(values.name); - } + // Local state for modals + const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); + const [addFundingModalOpen, setAddFundingModalOpen] = useState(false); + const [editFundingIndex, setEditFundingIndex] = useState(); + const [editContractIndex, setEditContractIndex] = useState(); - if (values.description) { - dispatch.projectEditor.updateDescription(values.description); - } + const projectId = + typeof projectIdPathParam === "string" ? projectIdPathParam : projectIdPathParam?.at(0); - if (values.publicGoodReason) { - dispatch.projectEditor.updatePublicGoodReason(values.publicGoodReason); - } - }, [values.description, values.publicGoodReason, values.name]); + const { + form, + values, + isSubmitting, + onSubmit, + updateTeamMembers, + updateCategories, + updateRepositories, + addRepository, + resetForm, + errors, + } = useProjectEditorForm({ + onSuccess: () => router.push(rootPathnames.PROJECTS_LIST), + }); + + const isOwner = values.isDao ? projectId === values.daoAddress : projectId === wallet?.accountId; const categoryChangeHandler = useCallback( - (categories: string[]) => { - dispatch.projectEditor.setCategories(categories); - form.setValue("categories", categories); - form.trigger(); // re-validate - }, - [form], + (categories: string[]) => updateCategories(categories), + [updateCategories], ); const onMembersChangeHandler = useCallback( - (members: string[]) => { - form.setValue("teamMembers", members); - }, - [form], + (members: string[]) => updateTeamMembers(members), + [updateTeamMembers], ); const onChangeRepositories = useCallback( - (repositories: string[]) => { - form.setValue("githubRepositories", repositories); - form.trigger(); // re-validate - }, - [form], + (repositories: string[]) => updateRepositories(repositories), + [updateRepositories], ); const resetUrl = useCallback(() => { router.push(rootPathnames.CREATE_PROJECT); - }, [router]); + resetForm(); + }, [router, resetForm]); - const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); - const [addFundingModalOpen, setAddFundingModalOpen] = useState(false); - const [editFundingIndex, setEditFundingIndex] = useState(); // controls if a funding is being edited - const [editContractIndex, setEditContractIndex] = useState(); + const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); + // const { isAuthenticated } = useSessionReduxStore(); + // const values = form.watch(); + + // useEffect(() => { + // // Set initial focus to name input. + // form.setFocus("name"); + // }, [form]); - const projectEditorText = projectTemplate.isEdit - ? projectTemplate.isDao - ? "Add proposal to update project" - : "Update your project" - : projectTemplate.isDao - ? "Add proposal to create project" - : "Create new project"; + // Set default values by profile + // useEffect(() => form.reset(projectTemplate), [form, projectTemplate]); - const isRepositoriesValid = projectTemplate.isRepositoryRequired - ? projectTemplate.githubRepositories - ? projectTemplate.githubRepositories?.length > 0 - : true - : true; + // Set initial name + // const [initialNameSet, setInitialNameSet] = useState(false); + + // useEffect(() => { + // if (!initialNameSet && projectTemplate.name) { + // form.setValue("name", projectTemplate.name); + // form.trigger(); // re-validate + // setInitialNameSet(true); + // } + // }, [initialNameSet, projectTemplate.name, form]); + + // // Store description, public good reason and daoAddress + // useEffect(() => { + // if (values.name) { + // dispatch.projectEditor.setProjectName(values.name); + // } + + // if (values.description) { + // dispatch.projectEditor.updateDescription(values.description); + // } + + // if (values.publicGoodReason) { + // dispatch.projectEditor.updatePublicGoodReason(values.publicGoodReason); + // } + // }, [values.description, values.publicGoodReason, values.name]); + + // const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); + // const [addFundingModalOpen, setAddFundingModalOpen] = useState(false); + // const [editFundingIndex, setEditFundingIndex] = useState(); // controls if a funding is being edited + // const [editContractIndex, setEditContractIndex] = useState(); + + const getProjectEditorText = () => { + if (projectTemplate.isEdit) { + return projectTemplate.isDao ? "Add proposal to update project" : "Update your project"; + } + + return projectTemplate.isDao ? "Add proposal to create project" : "Create new project"; + }; + + const projectEditorText = getProjectEditorText(); + + const isRepositoriesValid = + !projectTemplate.isRepositoryRequired || + (projectTemplate.githubRepositories && projectTemplate.githubRepositories.length > 0); // Wait for wallet - if (!isWalletReady) { + if (!isWalletReady || !wallet) { return ; } - if (isAuthenticated && projectTemplate.checkPreviousProjectDataStatus !== "ready") { - return ; - } + // if (isAuthenticated && projectTemplate.checkPreviousProjectDataStatus !== "ready") { + // return ; + // } - // must be signed in - if (!isAuthenticated) { - return ; - } + // // must be signed in + // if (!isAuthenticated) { + // return ; + // } // If it is Edit & not the owner if (!isOwner && projectTemplate.isEdit) { @@ -189,6 +201,8 @@ export const ProjectEditor = () => {
+ + {/* Team Member Section */} @@ -204,11 +218,10 @@ export const ProjectEditor = () => { + {/* MOdals */} { - setAddTeamModalOpen(false); - }} + onCloseClick={() => setAddTeamModalOpen(false)} onMembersChange={onMembersChangeHandler} /> From 3469a04ab8cc49744683289b30f8ba2250867aaa Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Thu, 16 Jan 2025 10:54:52 +0100 Subject: [PATCH 05/84] Reverting back some old code for testing --- .../components/ProjectEditor.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index c252f161..037b3751 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; @@ -7,7 +7,7 @@ import { prop } from "remeda"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { useWallet } from "@/entities/_shared/session"; -// import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; import { rootPathnames } from "@/pathnames"; import { dispatch, useGlobalStoreSelector } from "@/store"; @@ -88,7 +88,7 @@ export const ProjectEditor = () => { }, [router, resetForm]); const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); - // const { isAuthenticated } = useSessionReduxStore(); + const { isAuthenticated } = useSessionReduxStore(); // const values = form.watch(); // useEffect(() => { @@ -149,14 +149,14 @@ export const ProjectEditor = () => { return ; } - // if (isAuthenticated && projectTemplate.checkPreviousProjectDataStatus !== "ready") { - // return ; - // } + if (isAuthenticated && projectTemplate.checkPreviousProjectDataStatus !== "ready") { + return ; + } - // // must be signed in - // if (!isAuthenticated) { - // return ; - // } + // must be signed in + if (!isAuthenticated) { + return ; + } // If it is Edit & not the owner if (!isOwner && projectTemplate.isEdit) { @@ -258,7 +258,7 @@ export const ProjectEditor = () => { ( {
- + @@ -346,7 +400,7 @@ export const ProjectEditor = () => { - + {/* */} @@ -427,7 +427,7 @@ export const ProjectEditor = () => { disabled={!form.formState.isValid || isSubmitting} onClick={onSubmit} > - {projectEditorText} + {/* {projectEditorText} */} From f2818eca226fe98aae5b598081756469a210d43c Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 19 Jan 2025 19:21:11 +0000 Subject: [PATCH 09/84] wip --- src/common/contexts/wallet.ts | 0 src/entities/_shared/session/hooks/session.ts | 4 ++-- src/entities/_shared/session/hooks/wallet.ts | 6 ++++-- src/entities/_shared/session/index.ts | 2 ++ src/entities/_shared/session/types.ts | 6 +----- src/store/models.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 src/common/contexts/wallet.ts diff --git a/src/common/contexts/wallet.ts b/src/common/contexts/wallet.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/entities/_shared/session/hooks/session.ts b/src/entities/_shared/session/hooks/session.ts index aeabdd85..be6f5035 100644 --- a/src/entities/_shared/session/hooks/session.ts +++ b/src/entities/_shared/session/hooks/session.ts @@ -9,9 +9,9 @@ import { AccountId } from "@/common/types"; import { useGlobalStoreSelector } from "@/store"; import { useWallet } from "./wallet"; -import { UserSession } from "../types"; +import { Session } from "../types"; -export const useSession = (): UserSession => { +export const useSession = (): Session => { const { isSignedIn, wallet } = useWallet(); const { actAsDao, accountId: lastActiveAccountId } = useGlobalStoreSelector(prop("nav")); const asDao = actAsDao.toggle && Boolean(actAsDao.defaultAddress); diff --git a/src/entities/_shared/session/hooks/wallet.ts b/src/entities/_shared/session/hooks/wallet.ts index df22fb55..2b4fb548 100644 --- a/src/entities/_shared/session/hooks/wallet.ts +++ b/src/entities/_shared/session/hooks/wallet.ts @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; +import { WalletManager } from "@wpdas/naxios/dist/types/managers/wallet-manager"; + import { walletApi } from "@/common/api/near/client"; -import { Wallet } from "../types"; +export type Wallet = Omit; export const useWallet = () => { const [isWalletReady, setReady] = useState(false); @@ -28,4 +30,4 @@ export const useWallet = () => { }; }; -export default useWallet; +export type WalletContext = {}; diff --git a/src/entities/_shared/session/index.ts b/src/entities/_shared/session/index.ts index 2bcbeaf9..c1d2e5ef 100644 --- a/src/entities/_shared/session/index.ts +++ b/src/entities/_shared/session/index.ts @@ -9,3 +9,5 @@ export * from "./hooks/redux-store"; export * from "./hooks/session"; export * from "./hooks/wallet"; + +export { sessionModel } from "./model"; diff --git a/src/entities/_shared/session/types.ts b/src/entities/_shared/session/types.ts index cc1198c5..d90114ee 100644 --- a/src/entities/_shared/session/types.ts +++ b/src/entities/_shared/session/types.ts @@ -1,11 +1,7 @@ -import { WalletManager } from "@wpdas/naxios/dist/types/managers/wallet-manager"; - import type { RegistrationStatus } from "@/common/contracts/core/lists"; import { AccountId } from "@/common/types"; -export type Wallet = Omit; - -export type UserSession = +export type Session = | { accountId: AccountId; registrationStatus?: RegistrationStatus; diff --git a/src/store/models.ts b/src/store/models.ts index f6d32e91..831e63f5 100644 --- a/src/store/models.ts +++ b/src/store/models.ts @@ -1,7 +1,7 @@ import { Models, createModel } from "@rematch/core"; import { ContractMetadata } from "@/common/types"; -import { sessionModel } from "@/entities/_shared/session/model"; +import { sessionModel } from "@/entities/_shared/session"; import { campaignEditorModel } from "@/entities/campaign/models"; import { listEditorModel } from "@/entities/list"; import { donationModel, donationModelKey } from "@/features/donation"; From 900a0b98588d655e792978bda891af165abe7844 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 19 Jan 2025 20:50:02 +0000 Subject: [PATCH 10/84] wip --- .../api/indexer/internal/client.generated.ts | 2 + src/common/contexts/wallet-manager.tsx | 47 +++++++++++++++++++ src/common/contexts/wallet.ts | 0 .../_shared/session/components/buttons.tsx | 2 +- .../_shared/session/components/providers.tsx | 11 +---- .../_shared/session/hooks/redux-store.ts | 11 ----- src/entities/_shared/session/hooks/session.ts | 35 ++++++++------ src/entities/_shared/session/index.ts | 6 --- src/entities/_shared/session/model.ts | 31 ------------ src/entities/_shared/session/types.ts | 2 + .../token/components/TokenSelector.tsx | 4 +- src/entities/post/components/PostEditor.tsx | 6 +-- .../pot/components/ChallengeModal.tsx | 8 ++-- .../components/CandidateTable.tsx | 6 +-- src/entities/voting-round/hooks/candidates.ts | 21 ++++----- .../donation/components/DonationFlow.tsx | 4 +- .../donation/components/DonationModal.tsx | 6 +-- .../components/DonationTokenBalance.tsx | 4 +- .../pot-application/hooks/clearance.ts | 16 +++---- .../components/ProjectEditor.tsx | 21 +++------ .../components/payout-manager.tsx | 4 +- src/layout/components/app-bar.tsx | 9 ++-- src/layout/components/user-dropdown.tsx | 42 +++++++---------- src/layout/pot/components/layout-hero.tsx | 16 +++---- .../components/ProfileLayoutControls.tsx | 10 ++-- src/pages/_app.tsx | 30 ++++++------ src/pages/edit-project/[projectId].tsx | 6 +-- src/pages/index.tsx | 19 +++----- src/pages/pot/[potId]/votes.tsx | 10 ++-- src/pages/register.tsx | 6 +-- src/store/models.ts | 3 -- 31 files changed, 185 insertions(+), 213 deletions(-) create mode 100644 src/common/contexts/wallet-manager.tsx delete mode 100644 src/common/contexts/wallet.ts delete mode 100644 src/entities/_shared/session/hooks/redux-store.ts delete mode 100644 src/entities/_shared/session/model.ts diff --git a/src/common/api/indexer/internal/client.generated.ts b/src/common/api/indexer/internal/client.generated.ts index b8b7c13b..e38376c1 100644 --- a/src/common/api/indexer/internal/client.generated.ts +++ b/src/common/api/indexer/internal/client.generated.ts @@ -1128,6 +1128,8 @@ export interface ListRegistration { admin_notes?: string | null; /** Registration id. */ readonly id: number; + /** List registered. */ + readonly list_id: number; registered_by: Account; registrant: Account; /** diff --git a/src/common/contexts/wallet-manager.tsx b/src/common/contexts/wallet-manager.tsx new file mode 100644 index 00000000..d74babeb --- /dev/null +++ b/src/common/contexts/wallet-manager.tsx @@ -0,0 +1,47 @@ +import { createContext, useContext, useEffect, useMemo, useState } from "react"; + +import { WalletManager } from "@wpdas/naxios/dist/types/managers/wallet-manager"; + +import { nearClient } from "../api/near"; + +type WalletManagerContextState = + | { isReady: false } + | ({ isReady: true } & Omit); + +const initialWalletContextState: WalletManagerContextState = { isReady: false }; + +export const WalletManagerContext = + createContext(initialWalletContextState); + +export type WalletManagerContextProviderProps = { + children: React.ReactNode; +}; + +export const WalletManagerProvider: React.FC = ({ + children, +}) => { + const [isReady, setIsReady] = useState(false); + + useEffect(() => { + nearClient.walletApi + .initNear() + .then(() => setIsReady(true)) + .catch((error) => { + console.log(error); + setIsReady(false); + }); + }, []); + + const resolvedContext = useMemo( + () => (isReady ? { isReady, ...nearClient.walletApi } : initialWalletContextState), + [isReady], + ); + + return ( + + {children} + + ); +}; + +export const useWalletManagerContext = () => useContext(WalletManagerContext); diff --git a/src/common/contexts/wallet.ts b/src/common/contexts/wallet.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/entities/_shared/session/components/buttons.tsx b/src/entities/_shared/session/components/buttons.tsx index 5dfbee70..06d0213f 100644 --- a/src/entities/_shared/session/components/buttons.tsx +++ b/src/entities/_shared/session/components/buttons.tsx @@ -3,7 +3,7 @@ import { useCallback } from "react"; import { nearClient } from "@/common/api/near"; import { Button } from "@/common/ui/components"; -export const AuthSignInButton: React.FC = () => { +export const SessionAuthButton: React.FC = () => { const onClick = useCallback(() => { nearClient.walletApi.signInModal(); }, []); diff --git a/src/entities/_shared/session/components/providers.tsx b/src/entities/_shared/session/components/providers.tsx index 5d381798..fd7a3212 100644 --- a/src/entities/_shared/session/components/providers.tsx +++ b/src/entities/_shared/session/components/providers.tsx @@ -4,9 +4,7 @@ import { isClient } from "@wpdas/naxios"; import { walletApi } from "@/common/api/near/client"; import { SplashScreen } from "@/common/ui/components"; -import { dispatch, resetStore } from "@/store"; -import { useSessionReduxStore } from "../hooks/redux-store"; import { useWallet } from "../hooks/wallet"; type SessionProviderProps = { @@ -15,7 +13,7 @@ type SessionProviderProps = { export const SessionProvider = ({ children }: SessionProviderProps) => { const [isReady, setReady] = useState(false); - const { isAuthenticated } = useSessionReduxStore(); + const [isAuthenticated, setIsAuthenticated] = useState(false); const { wallet } = useWallet(); // Check wallet @@ -29,12 +27,7 @@ export const SessionProvider = ({ children }: SessionProviderProps) => { const isSignedIn = walletApi.walletSelector.isSignedIn(); if (isSignedIn !== isAuthenticated) { - dispatch.session.setAuthData({ isAuthenticated: isSignedIn }); - - if (!isSignedIn) { - // Clean up states - resetStore(); - } + setIsAuthenticated(isSignedIn); } } }, [isAuthenticated, isReady, wallet]); diff --git a/src/entities/_shared/session/hooks/redux-store.ts b/src/entities/_shared/session/hooks/redux-store.ts deleted file mode 100644 index a47cfdf9..00000000 --- a/src/entities/_shared/session/hooks/redux-store.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { prop } from "remeda"; - -import { useGlobalStoreSelector } from "@/store"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { useSession } from "./session"; - -/** - * @deprecated use {@link useSession} instead - */ -export const useSessionReduxStore = () => useGlobalStoreSelector(prop("session")); diff --git a/src/entities/_shared/session/hooks/session.ts b/src/entities/_shared/session/hooks/session.ts index be6f5035..8f12b144 100644 --- a/src/entities/_shared/session/hooks/session.ts +++ b/src/entities/_shared/session/hooks/session.ts @@ -3,22 +3,34 @@ import { useMemo } from "react"; import { prop } from "remeda"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { useWalletManagerContext } from "@/common/contexts/wallet-manager"; import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; import { AccountId } from "@/common/types"; import { useGlobalStoreSelector } from "@/store"; -import { useWallet } from "./wallet"; import { Session } from "../types"; +// TODO: Subscribe to wallet events to keep isSignedIn synced export const useSession = (): Session => { - const { isSignedIn, wallet } = useWallet(); + const walletManagerContext = useWalletManagerContext(); + + const isSignedIn = useMemo( + () => (walletManagerContext.isReady ? walletManagerContext.walletSelector.isSignedIn() : false), + [walletManagerContext], + ); + + const walletAccountId = useMemo( + () => (walletManagerContext.isReady ? walletManagerContext.accountId : null), + [walletManagerContext], + ); + const { actAsDao, accountId: lastActiveAccountId } = useGlobalStoreSelector(prop("nav")); const asDao = actAsDao.toggle && Boolean(actAsDao.defaultAddress); const accountId: AccountId | undefined = useMemo( - () => (asDao ? actAsDao.defaultAddress : (wallet?.accountId ?? lastActiveAccountId)), - [actAsDao.defaultAddress, asDao, lastActiveAccountId, wallet?.accountId], + () => (asDao ? actAsDao.defaultAddress : (walletAccountId ?? lastActiveAccountId)), + [actAsDao.defaultAddress, asDao, lastActiveAccountId, walletAccountId], ); /** @@ -27,16 +39,9 @@ export const useSession = (): Session => { */ const isAccountIdValid = useMemo(() => isAccountId(accountId), [accountId]); - const { isLoading: isRegistrationFlagLoading, data: isRegistered = false } = - listsContractHooks.useIsRegistered({ - enabled: isAccountIdValid, - accountId: accountId ?? "noop", - listId: PUBLIC_GOODS_REGISTRY_LIST_ID, - }); - const { isLoading: isRegistrationLoading, data: registration } = listsContractHooks.useRegistration({ - enabled: isRegistered, + enabled: isAccountIdValid, accountId: accountId ?? "noop", listId: PUBLIC_GOODS_REGISTRY_LIST_ID, }); @@ -45,17 +50,19 @@ export const useSession = (): Session => { return { accountId, isSignedIn: true, - isMetadataLoading: isRegistrationFlagLoading || isRegistrationLoading, + isMetadataLoading: isRegistrationLoading, + hasRegistrationSubmitted: registration !== undefined, hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, registrationStatus: registration?.status, }; } else { return { accountId: undefined, - registrationStatus: undefined, isSignedIn: false, isMetadataLoading: false, + hasRegistrationSubmitted: false, hasRegistrationApproved: false, + registrationStatus: undefined, }; } }; diff --git a/src/entities/_shared/session/index.ts b/src/entities/_shared/session/index.ts index c1d2e5ef..73f3ccbe 100644 --- a/src/entities/_shared/session/index.ts +++ b/src/entities/_shared/session/index.ts @@ -3,11 +3,5 @@ export * from "./types"; export * from "./components/buttons"; export * from "./components/providers"; -//! For backward compatibility only -// TODO: Rewire all the consumer code through `useSession` hook instead -export * from "./hooks/redux-store"; - export * from "./hooks/session"; export * from "./hooks/wallet"; - -export { sessionModel } from "./model"; diff --git a/src/entities/_shared/session/model.ts b/src/entities/_shared/session/model.ts deleted file mode 100644 index 6a2943db..00000000 --- a/src/entities/_shared/session/model.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createModel } from "@rematch/core"; - -import { AppModel } from "@/store/models"; - -interface AuthState { - isAuthenticated: boolean; -} - -const initialState: AuthState = { - isAuthenticated: false, -}; - -export const sessionModel = createModel()({ - state: initialState, - - reducers: { - SET_DATA(state: AuthState, { isAuthenticated }: AuthState) { - state.isAuthenticated = isAuthenticated; - }, - - RESET() { - return initialState; - }, - }, - - effects: (dispatch) => ({ - setAuthData(props: AuthState) { - dispatch.session.SET_DATA(props); - }, - }), -}); diff --git a/src/entities/_shared/session/types.ts b/src/entities/_shared/session/types.ts index d90114ee..dd0a5681 100644 --- a/src/entities/_shared/session/types.ts +++ b/src/entities/_shared/session/types.ts @@ -7,6 +7,7 @@ export type Session = registrationStatus?: RegistrationStatus; isSignedIn: true; isMetadataLoading: boolean; + hasRegistrationSubmitted: boolean; hasRegistrationApproved: boolean; } | { @@ -14,5 +15,6 @@ export type Session = registrationStatus: undefined; isSignedIn: false; isMetadataLoading: false; + hasRegistrationSubmitted: false; hasRegistrationApproved: false; }; diff --git a/src/entities/_shared/token/components/TokenSelector.tsx b/src/entities/_shared/token/components/TokenSelector.tsx index 7126e858..c8c33c1f 100644 --- a/src/entities/_shared/token/components/TokenSelector.tsx +++ b/src/entities/_shared/token/components/TokenSelector.tsx @@ -6,11 +6,11 @@ import { useSession } from "@/entities/_shared/session"; import { useToken, useTokenAllowlist } from "../hooks"; const TokenSelectorOption: React.FC = ({ tokenId }) => { - const authenticatedUser = useSession(); + const viewer = useSession(); const { data: token } = useToken({ tokenId, - balanceCheckAccountId: authenticatedUser?.accountId, + balanceCheckAccountId: viewer?.accountId, }); switch (tokenId) { diff --git a/src/entities/post/components/PostEditor.tsx b/src/entities/post/components/PostEditor.tsx index cd8e4782..41facfe9 100644 --- a/src/entities/post/components/PostEditor.tsx +++ b/src/entities/post/components/PostEditor.tsx @@ -9,11 +9,11 @@ import { useAccountSocialProfile } from "@/entities/_shared/account"; import { useSession } from "@/entities/_shared/session"; export const PostEditor = ({ accountId }: { accountId: AccountId }) => { - const authenticatedUser = useSession(); + const viewer = useSession(); const { avatarSrc } = useAccountSocialProfile({ - enabled: authenticatedUser.isSignedIn, - accountId: authenticatedUser.accountId as AccountId, + enabled: viewer.isSignedIn, + accountId: viewer.accountId as AccountId, }); const [postText, setPostText] = useState(""); diff --git a/src/entities/pot/components/ChallengeModal.tsx b/src/entities/pot/components/ChallengeModal.tsx index f5cda0f9..d6e3c547 100644 --- a/src/entities/pot/components/ChallengeModal.tsx +++ b/src/entities/pot/components/ChallengeModal.tsx @@ -30,16 +30,16 @@ export const ChallengeModal: React.FC = ({ potId, potDetail, }) => { - const authenticatedUser = useSession(); + const viewer = useSession(); const { data: potPayoutChallenges } = potContractHooks.usePayoutChallenges({ potId }); const activeChallenge = useMemo(() => { - if (authenticatedUser.isSignedIn) { + if (viewer.isSignedIn) { return (potPayoutChallenges ?? []).find( - ({ challenger_id }) => authenticatedUser.accountId === challenger_id, + ({ challenger_id }) => viewer.accountId === challenger_id, ); } else return undefined; - }, [authenticatedUser.isSignedIn, authenticatedUser.accountId, potPayoutChallenges]); + }, [viewer.isSignedIn, viewer.accountId, potPayoutChallenges]); // Form settings const { form, errors, onSubmit, inProgress } = useChallengeForm({ potDetail }); diff --git a/src/entities/voting-round/components/CandidateTable.tsx b/src/entities/voting-round/components/CandidateTable.tsx index 23801162..49c33da6 100644 --- a/src/entities/voting-round/components/CandidateTable.tsx +++ b/src/entities/voting-round/components/CandidateTable.tsx @@ -30,7 +30,7 @@ export const VotingRoundCandidateTable: React.FC }) => { const { height: windowHeight } = useWindowSize(); const { toast } = useToast(); - const authenticatedUser = useSession(); + const viewer = useSession(); const selectedEntries = useSet(); const { data: isVotingPeriodOngoing } = votingContractHooks.useIsVotingPeriod({ @@ -39,9 +39,9 @@ export const VotingRoundCandidateTable: React.FC }); const { data: remainingUserVotingCapacity } = votingContractHooks.useVoterRemainingCapacity({ - enabled: electionId !== 0 && authenticatedUser.accountId !== undefined, + enabled: electionId !== 0 && viewer.accountId !== undefined, electionId, - accountId: authenticatedUser.accountId as AccountId, + accountId: viewer.accountId as AccountId, }); const handleEntrySelect = useCallback( diff --git a/src/entities/voting-round/hooks/candidates.ts b/src/entities/voting-round/hooks/candidates.ts index 2a27a2e6..45937f70 100644 --- a/src/entities/voting-round/hooks/candidates.ts +++ b/src/entities/voting-round/hooks/candidates.ts @@ -15,7 +15,7 @@ export interface VotingRoundCandidateLookup extends ByElectionId {} export const useVotingRoundCandidateLookup = ({ electionId }: VotingRoundCandidateLookup) => { const [searchTerm, setSearchTerm] = useState(""); - const authenticatedUser = useSession(); + const viewer = useSession(); const { data, ...candidatesQueryResult } = votingContractHooks.useElectionCandidates({ enabled: electionId !== 0, @@ -23,9 +23,9 @@ export const useVotingRoundCandidateLookup = ({ electionId }: VotingRoundCandida }); const { data: userVotes } = votingContractHooks.useVotingRoundVoterVotes({ - enabled: electionId !== 0 && isAccountId(authenticatedUser.accountId), + enabled: electionId !== 0 && isAccountId(viewer.accountId), electionId, - accountId: authenticatedUser.accountId as AccountId, + accountId: viewer.accountId as AccountId, }); return useMemo(() => { @@ -67,7 +67,7 @@ export const useVotingRoundCandidateEntry = ({ accountId, }: ByElectionId & ByAccountId) => { const { toast } = useToast(); - const authenticatedUser = useSession(); + const viewer = useSession(); const { data: isVotingPeriodOngoing = false } = votingContractHooks.useIsVotingPeriod({ enabled: electionId !== 0, @@ -76,9 +76,9 @@ export const useVotingRoundCandidateEntry = ({ const { data: remainingUserVotingCapacity, isLoading: isRemainingUserVotingCapacityLoading } = votingContractHooks.useVoterRemainingCapacity({ - enabled: electionId !== 0 && isAccountId(authenticatedUser.accountId), + enabled: electionId !== 0 && isAccountId(viewer.accountId), electionId, - accountId: authenticatedUser.accountId as AccountId, + accountId: viewer.accountId as AccountId, }); const { @@ -101,23 +101,22 @@ export const useVotingRoundCandidateEntry = ({ const hasUserVotes = useMemo( () => - votes?.find(({ voter: voterAccountId }) => voterAccountId === authenticatedUser.accountId) !== - undefined, + votes?.find(({ voter: voterAccountId }) => voterAccountId === viewer.accountId) !== undefined, - [votes, authenticatedUser.accountId], + [votes, viewer.accountId], ); const canReceiveVotes = useMemo( () => electionId !== 0 && - authenticatedUser.isSignedIn && + viewer.isSignedIn && (votes === undefined ? false : isVotingPeriodOngoing && !hasUserVotes && remainingUserVotingCapacity !== 0), [ electionId, - authenticatedUser.isSignedIn, + viewer.isSignedIn, votes, isVotingPeriodOngoing, hasUserVotes, diff --git a/src/features/donation/components/DonationFlow.tsx b/src/features/donation/components/DonationFlow.tsx index 26ee3bb1..147a973f 100644 --- a/src/features/donation/components/DonationFlow.tsx +++ b/src/features/donation/components/DonationFlow.tsx @@ -25,7 +25,7 @@ export const DonationFlow: React.FC = ({ closeModal, ...props }) => { - const authenticatedUser = useSession(); + const viewer = useSession(); const { currentStep, finalOutcome } = useDonationState(); const { @@ -46,7 +46,7 @@ export const DonationFlow: React.FC = ({ const { data: token } = useToken({ tokenId, - balanceCheckAccountId: authenticatedUser?.accountId, + balanceCheckAccountId: viewer?.accountId, }); const isBalanceSufficient = isBigSource(totalAmountFloat) diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index 12abe24d..8a49d1db 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -6,7 +6,7 @@ import { walletApi } from "@/common/api/near/client"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSessionReduxStore } from "@/entities/_shared/session"; +import { useSession } from "@/entities/_shared/session"; import { dispatch } from "@/store"; import { DonationFlow, DonationFlowProps } from "./DonationFlow"; @@ -23,7 +23,7 @@ export const DonationModal = create((props: DonationModalProps) => { const isListDonation = "listId" in props; const isCampaignDonation = "campaignId" in props; const { currentStep } = useDonationState(); - const { isAuthenticated } = useSessionReduxStore(); + const viewer = useSession(); const { setSearchParams } = useRouteQuery(); const close = useCallback(() => { @@ -63,7 +63,7 @@ export const DonationModal = create((props: DonationModalProps) => { } onCloseClick={close} > - {!isAuthenticated ? ( + {!viewer.isSignedIn ? ( = ({ tokenId, classNames, }) => { - const authenticatedUser = useSession(); + const viewer = useSession(); const { data: token, error: tokenError } = useToken({ - balanceCheckAccountId: authenticatedUser?.accountId, + balanceCheckAccountId: viewer?.accountId, tokenId, }); diff --git a/src/features/pot-application/hooks/clearance.ts b/src/features/pot-application/hooks/clearance.ts index 42a9a417..621932e9 100644 --- a/src/features/pot-application/hooks/clearance.ts +++ b/src/features/pot-application/hooks/clearance.ts @@ -24,15 +24,15 @@ export const usePotApplicationUserClearance = ({ const { staking } = POT_APPLICATION_REQUIREMENTS_MPDAO; const { data: pot } = indexer.usePot({ potId }); - const authenticatedUser = useSession(); + const viewer = useSession(); const { data: voterInfo } = indexer.useMpdaoVoter({ - enabled: authenticatedUser.isSignedIn, - accountId: authenticatedUser.accountId as AccountId, + enabled: viewer.isSignedIn, + accountId: viewer.accountId as AccountId, }); const { data: stakingToken } = useToken({ - balanceCheckAccountId: authenticatedUser.accountId, + balanceCheckAccountId: viewer.accountId, tokenId: staking.tokenId, }); @@ -42,8 +42,8 @@ export const usePotApplicationUserClearance = ({ ? [ { title: `Verified Project on ${PLATFORM_NAME}`, - isFulfillmentAssessmentPending: authenticatedUser.isMetadataLoading, - isSatisfied: authenticatedUser.hasRegistrationApproved, + isFulfillmentAssessmentPending: viewer.isMetadataLoading, + isSatisfied: viewer.hasRegistrationApproved, }, ] : []), @@ -76,8 +76,8 @@ export const usePotApplicationUserClearance = ({ error: null, }; }, [ - authenticatedUser.isMetadataLoading, - authenticatedUser.hasRegistrationApproved, + viewer.isMetadataLoading, + viewer.hasRegistrationApproved, hasProportionalFundingMechanism, pot?.sybil_wrapper_provider, staking.minAmountUsd, diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index d9f0ac89..fbc9c341 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -6,8 +6,7 @@ import { prop } from "remeda"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; -import { useWallet } from "@/entities/_shared/session"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSession, useWallet } from "@/entities/_shared/session"; import { rootPathnames } from "@/pathnames"; import { dispatch, useGlobalStoreSelector } from "@/store"; @@ -42,14 +41,11 @@ export const ProjectEditor = () => { typeof projectIdPathParam === "string" ? projectIdPathParam : projectIdPathParam?.at(0); const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); - const { wallet, isWalletReady } = useWallet(); - const { isAuthenticated } = useSessionReduxStore(); + const viewer = useSession(); const { form, errors, onSubmit } = useProjectEditorForm(); const values = form.watch(); - const isOwner = projectTemplate.isDao - ? projectId === projectTemplate.daoAddress - : projectId === wallet?.accountId; + const isOwner = projectId === viewer.accountId; useEffect(() => { // Set initial focus to name input. @@ -132,17 +128,12 @@ export const ProjectEditor = () => { : true : true; - // Wait for wallet - if (!isWalletReady) { - return ; - } - - if (isAuthenticated && projectTemplate.checkPreviousProjectDataStatus !== "ready") { + if (viewer.isSignedIn && projectTemplate.checkPreviousProjectDataStatus !== "ready") { return ; } // must be signed in - if (!isAuthenticated) { + if (!viewer.isSignedIn) { return ; } @@ -173,7 +164,7 @@ export const ProjectEditor = () => {
diff --git a/src/features/proportional-funding/components/payout-manager.tsx b/src/features/proportional-funding/components/payout-manager.tsx index 4b63d6d5..d7b6b214 100644 --- a/src/features/proportional-funding/components/payout-manager.tsx +++ b/src/features/proportional-funding/components/payout-manager.tsx @@ -22,8 +22,8 @@ export const ProportionalFundingPayoutManager: React.FC { const { toast } = useToast(); - const authenticatedUser = useSession(); - const authorizedUser = usePotAuthorization({ potId, accountId: authenticatedUser.accountId }); + const viewer = useSession(); + const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); const votingRoundResults = useVotingRoundResults({ potId }); const { isMetadataLoading: isTokenMetadataLoading, data: token } = useToken({ diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index 1e7b1928..ed6277f2 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -7,7 +7,7 @@ import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; import useIsClient from "@/common/lib/useIsClient"; import { cn } from "@/common/ui/utils"; -import { AuthSignInButton, useSessionReduxStore } from "@/entities/_shared/session"; +import { SessionAuthButton, useSession } from "@/entities/_shared/session"; import { CartLink } from "@/entities/cart"; import { rootPathnames } from "@/pathnames"; @@ -33,12 +33,13 @@ const links = [ ]; const AuthButton = () => { - const { isAuthenticated } = useSessionReduxStore(); - const isClient = useIsClient(); + const viewer = useSession(); + // TODO: Investigate if it's really necessary + const isClient = useIsClient(); if (!isClient) return; - return isAuthenticated ? : ; + return viewer.isSignedIn ? : ; }; const MobileMenuButton = ({ onClick }: { onClick: () => void }) => { diff --git a/src/layout/components/user-dropdown.tsx b/src/layout/components/user-dropdown.tsx index 2e3db3d3..1631b8e1 100644 --- a/src/layout/components/user-dropdown.tsx +++ b/src/layout/components/user-dropdown.tsx @@ -24,11 +24,11 @@ import { DaoAuth } from "./dao-auth"; // TODO: Finish refactoring export const UserDropdown = () => { - const authenticatedUser = useSession(); + const viewer = useSession(); const { profile } = useAccountSocialProfile({ - enabled: authenticatedUser.isSignedIn, - accountId: authenticatedUser.accountId ?? "noop", + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", }); const logoutHandler = useCallback(() => { @@ -38,30 +38,27 @@ export const UserDropdown = () => { return ( - {authenticatedUser.isSignedIn ? ( - + {viewer.isSignedIn ? ( + ) : ( )} - {authenticatedUser.registrationStatus && ( + {viewer.registrationStatus && ( - {authenticatedUser.registrationStatus} + {viewer.registrationStatus} @@ -70,20 +67,15 @@ export const UserDropdown = () => {
- {authenticatedUser.accountId && ( - + {viewer.accountId && ( + )}
{profile?.name &&

{truncate(profile.name, 30)}

} - {authenticatedUser.accountId && ( -

- {truncate(authenticatedUser.accountId, 30)} -

+ {viewer.accountId && ( +

{truncate(viewer.accountId, 30)}

)}
@@ -91,10 +83,10 @@ export const UserDropdown = () => {
- {authenticatedUser.accountId && ( - + {viewer.accountId && ( + - {authenticatedUser.hasRegistrationApproved ? "My Project" : "My Profile"} + {viewer.hasRegistrationApproved ? "My Project" : "My Profile"} )} diff --git a/src/layout/pot/components/layout-hero.tsx b/src/layout/pot/components/layout-hero.tsx index f8608a50..dab9f736 100644 --- a/src/layout/pot/components/layout-hero.tsx +++ b/src/layout/pot/components/layout-hero.tsx @@ -37,8 +37,8 @@ export const PotLayoutHero: React.FC = ({ onChallengePayoutsClick, onFundMatchingPoolClick, }) => { - const authenticatedUser = useSession(); - const authorizedUser = usePotAuthorization({ potId, accountId: authenticatedUser.accountId }); + const viewer = useSession(); + const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); const { data: pot } = indexer.usePot({ potId }); const { data: potPayoutChallenges } = potContractHooks.usePayoutChallenges({ potId }); const { hasProportionalFundingMechanism } = usePotFeatureFlags({ potId }); @@ -46,13 +46,13 @@ export const PotLayoutHero: React.FC = ({ const activeChallenge = useMemo( () => - authenticatedUser.isSignedIn + viewer.isSignedIn ? (potPayoutChallenges ?? []).find( - ({ challenger_id }) => authenticatedUser.accountId === challenger_id, + ({ challenger_id }) => viewer.accountId === challenger_id, ) : undefined, - [authenticatedUser.isSignedIn, authenticatedUser.accountId, potPayoutChallenges], + [viewer.isSignedIn, viewer.accountId, potPayoutChallenges], ); const applicationClearance = usePotApplicationUserClearance({ @@ -65,10 +65,8 @@ export const PotLayoutHero: React.FC = ({ [lifecycle.currentStage?.tag], ); - const referrerPotLink = authenticatedUser.isSignedIn - ? window.location.origin + - window.location.pathname + - `&referrerId=${authenticatedUser.accountId}` + const referrerPotLink = viewer.isSignedIn + ? window.location.origin + window.location.pathname + `&referrerId=${viewer.accountId}` : null; const [description, linkedDocumentUrl] = useMemo(() => { diff --git a/src/layout/profile/components/ProfileLayoutControls.tsx b/src/layout/profile/components/ProfileLayoutControls.tsx index 2978ce8b..b7f3914f 100644 --- a/src/layout/profile/components/ProfileLayoutControls.tsx +++ b/src/layout/profile/components/ProfileLayoutControls.tsx @@ -25,18 +25,18 @@ type Props = { }; const LinksWrapper = ({ accountId }: { accountId: string }) => { - const authenticatedUser = useSession(); + const viewer = useSession(); const [copied, setCopied] = useState(false); return (
- {authenticatedUser.isSignedIn && ( + {viewer.isSignedIn && ( { setCopied(true); @@ -144,8 +144,8 @@ const DonationsInfo = ({ accountId }: { accountId: string }) => { }; export const ProfileLayoutControls = ({ accountId, isProject }: Props) => { - const authenticatedUser = useSession(); - const isOwner = authenticatedUser?.accountId === accountId; + const viewer = useSession(); + const isOwner = viewer?.accountId === accountId; const { profile } = useAccountSocialProfile({ accountId }); return ( diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 8f8d8a41..5bd6f5f6 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -17,10 +17,10 @@ import Head from "next/head"; import { Provider as ReduxProvider } from "react-redux"; import { APP_METADATA } from "@/common/constants"; +import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; import { TooltipProvider } from "@/common/ui/components"; import { Toaster } from "@/common/ui/components/molecules/toaster"; import { cn } from "@/common/ui/utils"; -import { SessionProvider } from "@/entities/_shared/session"; import { AppBar } from "@/layout/components/app-bar"; import { dispatch, store } from "@/store"; @@ -44,7 +44,7 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) const getLayout = Component.getLayout ?? ((page) => page); return ( - <> + {APP_METADATA.title} @@ -52,22 +52,20 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) - - -
- - {getLayout()} -
-
-
+ +
+ + {getLayout()} +
+
- +
); } diff --git a/src/pages/edit-project/[projectId].tsx b/src/pages/edit-project/[projectId].tsx index b457d3c8..c2ef36c7 100644 --- a/src/pages/edit-project/[projectId].tsx +++ b/src/pages/edit-project/[projectId].tsx @@ -1,18 +1,18 @@ import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSession } from "@/entities/_shared/session"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; export default function EditProjectPage() { - const { isAuthenticated } = useSessionReduxStore(); + const viewer = useSession(); useInitProjectState(); // state used to show spinner during the data post const { submissionStatus, checkRegistrationStatus, checkPreviousProjectDataStatus } = useGlobalStoreSelector((state) => state.projectEditor); - const showSpinner = isAuthenticated + const showSpinner = viewer.isSignedIn ? submissionStatus === "sending" || checkRegistrationStatus !== "ready" || checkPreviousProjectDataStatus !== "ready" diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8f8bad40..64917d55 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,16 +1,13 @@ import Link from "next/link"; import { NETWORK } from "@/common/_config"; -import { useRegistration } from "@/common/_deprecated/useRegistration"; import { indexer } from "@/common/api/indexer"; -import { nearClient } from "@/common/api/near"; import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSessionReduxStore } from "@/entities/_shared/session"; +import { useSession } from "@/entities/_shared/session"; import { ProjectCard, ProjectDiscovery } from "@/entities/project"; import { DonateRandomly } from "@/features/donation"; import routesPath from "@/pathnames"; -import { useGlobalStoreSelector } from "@/store"; export const FEATURED_PROJECT_ACCOUNT_IDS = NETWORK === "mainnet" @@ -47,11 +44,7 @@ export const GeneralStats = () => { }; const WelcomeBanner = () => { - const { defaultAddress, toggle } = useGlobalStoreSelector((state) => state.nav.actAsDao); - const daoAddress = toggle && defaultAddress ? defaultAddress : ""; - const accountId = daoAddress || nearClient.walletApi?.accountId || ""; - const { isAuthenticated } = useSessionReduxStore(); - const { loading, isRegisteredProject } = useRegistration(accountId); + const viewer = useSession(); return (
{
- {isAuthenticated && !loading && ( + {!viewer.isMetadataLoading && viewer.isSignedIn && ( )} diff --git a/src/pages/pot/[potId]/votes.tsx b/src/pages/pot/[potId]/votes.tsx index b7ffab9d..aeafd695 100644 --- a/src/pages/pot/[potId]/votes.tsx +++ b/src/pages/pot/[potId]/votes.tsx @@ -39,7 +39,7 @@ import { import { PotLayout } from "@/layout/pot/components/layout"; export default function PotVotesTab() { - const authenticatedUser = useSession(); + const viewer = useSession(); const { query: routeQuery } = useRouter(); const { potId } = routeQuery as { potId: string }; const isDesktop = useMediaQuery("(min-width: 1024px)"); @@ -54,7 +54,7 @@ export default function PotVotesTab() { const [candidateFilter, setFilter] = useState("all"); const authenticatedVoter = useVotingRoundVoterVoteWeight({ - accountId: authenticatedUser.accountId, + accountId: viewer.accountId, potId, }); @@ -69,9 +69,9 @@ export default function PotVotesTab() { const { data: authenticatedVotingRoundVoterVotes } = votingContractHooks.useVotingRoundVoterVotes( { - enabled: votingRound !== undefined && isAccountId(authenticatedUser.accountId), + enabled: votingRound !== undefined && isAccountId(viewer.accountId), electionId: votingRound?.electionId ?? 0, - accountId: authenticatedUser.accountId as AccountId, + accountId: viewer.accountId as AccountId, }, ); @@ -199,7 +199,7 @@ export default function PotVotesTab() {
- {authenticatedUser.isSignedIn ? ( + {viewer.isSignedIn ? ( {isVotingPeriodOngoing ? ( <> diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 21b9c144..eb69dd3f 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,18 +1,18 @@ import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSession } from "@/entities/_shared/session"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; export default function RegisterPage() { - const { isAuthenticated } = useSessionReduxStore(); + const viewer = useSession(); useInitProjectState(); // state used to show spinner during the data post const { submissionStatus, checkRegistrationStatus, checkPreviousProjectDataStatus } = useGlobalStoreSelector((state) => state.projectEditor); - const showSpinner = isAuthenticated + const showSpinner = viewer.isSignedIn ? submissionStatus === "sending" || checkRegistrationStatus !== "ready" || checkPreviousProjectDataStatus !== "ready" diff --git a/src/store/models.ts b/src/store/models.ts index 831e63f5..cd0c0139 100644 --- a/src/store/models.ts +++ b/src/store/models.ts @@ -1,7 +1,6 @@ import { Models, createModel } from "@rematch/core"; import { ContractMetadata } from "@/common/types"; -import { sessionModel } from "@/entities/_shared/session"; import { campaignEditorModel } from "@/entities/campaign/models"; import { listEditorModel } from "@/entities/list"; import { donationModel, donationModelKey } from "@/features/donation"; @@ -53,7 +52,6 @@ export const coreModel = createModel()({ export interface AppModel extends Models { core: typeof coreModel; - session: typeof sessionModel; [donationModelKey]: typeof donationModel; nav: typeof navModel; [potConfigurationModelKey]: typeof potConfigurationModel; @@ -64,7 +62,6 @@ export interface AppModel extends Models { export const models: AppModel = { core: coreModel, - session: sessionModel, [donationModelKey]: donationModel, nav: navModel, listEditor: listEditorModel, From 239e6ab09103f8c1ff69a964e18738bc83fa4aab Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 19 Jan 2025 21:09:16 +0000 Subject: [PATCH 11/84] Disable SSR --- src/pages/_app.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 5bd6f5f6..ed29c0ee 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -10,6 +10,7 @@ import "@/common/ui/styles/uno.generated.css"; import { useEffect } from "react"; import { Provider as NiceModalProvider } from "@ebay/nice-modal-react"; +import { isClient } from "@wpdas/naxios"; import { NextPage } from "next"; import { AppProps } from "next/app"; import { Lora } from "next/font/google"; @@ -18,7 +19,7 @@ import { Provider as ReduxProvider } from "react-redux"; import { APP_METADATA } from "@/common/constants"; import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; -import { TooltipProvider } from "@/common/ui/components"; +import { SplashScreen, TooltipProvider } from "@/common/ui/components"; import { Toaster } from "@/common/ui/components/molecules/toaster"; import { cn } from "@/common/ui/utils"; import { AppBar } from "@/layout/components/app-bar"; @@ -44,7 +45,7 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) const getLayout = Component.getLayout ?? ((page) => page); return ( - + <> {APP_METADATA.title} @@ -59,13 +60,19 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) lora.variable, )} > - - {getLayout()} + {isClient() ? ( + + + {getLayout()} + + ) : ( + + )}
- + ); } From 1b463ed781331a84f8321bca6d556796ec314440 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 19 Jan 2025 21:22:31 +0000 Subject: [PATCH 12/84] Stop using localStorage cache --- src/common/contracts/social/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/contracts/social/client.ts b/src/common/contracts/social/client.ts index b1419c75..0d9030ce 100644 --- a/src/common/contracts/social/client.ts +++ b/src/common/contracts/social/client.ts @@ -1,4 +1,4 @@ -import { StorageCache, buildTransaction } from "@wpdas/naxios"; +import { MemoryCache, buildTransaction } from "@wpdas/naxios"; import { SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; import { naxiosInstance } from "@/common/api/near/client"; @@ -9,7 +9,7 @@ import { AccountId } from "@/common/types"; */ const nearSocialDbContractApi = naxiosInstance.contractApi({ contractId: SOCIAL_DB_CONTRACT_ACCOUNT_ID, - cache: new StorageCache({ expirationTime: 5 * 60 }), // 5 minutes + cache: new MemoryCache({ expirationTime: 5 * 60 }), // 5 minutes }); interface NEARSocialUserProfileInput { From 86884c9611a0b862c4df9f7e4ad697472124c3e4 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 19 Jan 2025 22:44:32 +0000 Subject: [PATCH 13/84] wip --- package.json | 3 +- src/common/contexts/wallet-manager.tsx | 2 +- .../campaign/components/CampaignLayout.tsx | 44 ----- src/entities/campaign/components/index.ts | 6 - src/entities/campaign/constants.ts | 13 -- src/entities/campaign/index.ts | 9 + src/entities/list/index.ts | 4 + src/entities/pot/components/PotCard.tsx | 4 +- .../pot/components/PotDonationEntry.tsx | 8 +- .../pot/components/PotPayoutChallenges.tsx | 4 +- .../pot/components/PotSponsorsTable.tsx | 10 +- .../project/components/ProjectCard.tsx | 4 +- src/entities/project/components/Team.tsx | 4 +- .../donation/components/DonationSuccess.tsx | 6 +- .../components/PotDeploymentSuccess.tsx | 4 +- .../pot-deployment/components/buttons.tsx | 33 ++++ src/features/pot-deployment/index.ts | 1 + .../components/SuccessfulRegister.tsx | 6 +- .../hooks/useInitProjectState.ts | 4 +- .../campaign/components/layout.tsx} | 57 +++++- src/layout/components/app-bar.tsx | 20 +-- src/layout/components/user-dropdown.tsx | 1 - src/layout/pot/components/layout.tsx | 166 +++++++++--------- .../profile/_deprecated/DonationItem.tsx | 8 +- .../components/ProfileLayoutControls.tsx | 6 +- .../{ProfileLayout.tsx => layout.tsx} | 48 ++--- src/pages/_app.tsx | 14 +- src/pages/campaign/[campaignId]/history.tsx | 4 +- .../campaign/[campaignId]/leaderboard.tsx | 3 +- src/pages/campaign/[campaignId]/settings.tsx | 3 +- src/pages/campaign/create.tsx | 18 +- src/pages/deploy.tsx | 53 +++--- src/pages/edit-project/[projectId].tsx | 13 +- src/pages/feed/[account]/[block]/index.tsx | 15 +- src/pages/feed/index.tsx | 13 +- src/pages/index.tsx | 6 +- src/pages/list/create/index.tsx | 17 +- src/pages/list/duplicate/[id].tsx | 17 +- src/pages/list/edit/[id]/index.tsx | 17 +- src/pages/pots.tsx | 49 ++---- src/pages/profile/[userId]/campaigns.tsx | 4 +- src/pages/profile/[userId]/donations.tsx | 2 +- src/pages/profile/[userId]/feed.tsx | 2 +- src/pages/profile/[userId]/funding-raised.tsx | 2 +- src/pages/profile/[userId]/home.tsx | 2 +- src/pages/profile/[userId]/lists.tsx | 2 +- src/pages/profile/[userId]/pots.tsx | 2 +- src/pathnames.ts | 5 - 48 files changed, 422 insertions(+), 316 deletions(-) delete mode 100644 src/entities/campaign/components/CampaignLayout.tsx delete mode 100644 src/entities/campaign/components/index.ts delete mode 100644 src/entities/campaign/constants.ts create mode 100644 src/entities/campaign/index.ts create mode 100644 src/features/pot-deployment/components/buttons.tsx create mode 100644 src/features/pot-deployment/index.ts rename src/{entities/campaign/components/Tabs.tsx => layout/campaign/components/layout.tsx} (59%) rename src/layout/profile/components/{ProfileLayout.tsx => layout.tsx} (81%) diff --git a/package.json b/package.json index a7e338ee..2363a3fc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "next:dev": "yarn && next dev", "build": "yarn generate:css && next build", "start": "next start", + "preview": "next build && next start", "test:unit": "vitest run", "prepare": "husky" }, @@ -157,4 +158,4 @@ "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0" } -} +} \ No newline at end of file diff --git a/src/common/contexts/wallet-manager.tsx b/src/common/contexts/wallet-manager.tsx index d74babeb..6936ec26 100644 --- a/src/common/contexts/wallet-manager.tsx +++ b/src/common/contexts/wallet-manager.tsx @@ -2,7 +2,7 @@ import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { WalletManager } from "@wpdas/naxios/dist/types/managers/wallet-manager"; -import { nearClient } from "../api/near"; +import { nearClient } from "@/common/api/near"; type WalletManagerContextState = | { isReady: false } diff --git a/src/entities/campaign/components/CampaignLayout.tsx b/src/entities/campaign/components/CampaignLayout.tsx deleted file mode 100644 index 5970801d..00000000 --- a/src/entities/campaign/components/CampaignLayout.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useCallback, useState } from "react"; - -import { useRouter } from "next/router"; - -import { PageWithBanner } from "@/common/ui/components"; - -import { CAMPAIGN_TAB_ROUTES } from "../constants"; -import { SingleCampaignBanner } from "./SingleCampaignBanner"; -import Tabs from "./Tabs"; - -type ReactLayoutProps = { - children: React.ReactNode; -}; - -export const CampaignLayout: React.FC = ({ children }) => { - const router = useRouter(); - const pathname = router.pathname; - - const tabs = CAMPAIGN_TAB_ROUTES; - - const [selectedTab, setSelectedTab] = useState( - tabs.find((tab) => pathname.includes(tab.href)) || tabs[0], - ); - - const handleSelectedTab = useCallback( - (tabId: string) => setSelectedTab(tabs.find((tabRoute) => tabRoute.id === tabId)!), - [tabs], - ); - - return ( - -
- -
- handleSelectedTab(tabId)} - /> -
{children}
-
- ); -}; diff --git a/src/entities/campaign/components/index.ts b/src/entities/campaign/components/index.ts deleted file mode 100644 index 6d683a9f..00000000 --- a/src/entities/campaign/components/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./CampaignCard"; -export * from "./CampaignProgressBar"; -export * from "./CampaignLayout"; -export * from "./SingleCampaignBanner"; -export * from "./CampaignDonorsTable"; -export * from "./CampaignSettings"; diff --git a/src/entities/campaign/constants.ts b/src/entities/campaign/constants.ts deleted file mode 100644 index 255bd0c7..00000000 --- a/src/entities/campaign/constants.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { TabOption } from "@/common/ui/types"; - -export const CAMPAIGN_TAB_ROUTES: TabOption[] = [ - { - label: "Leaderboard", - id: "leaderboard", - href: "/leaderboard", - }, - - // { label: "History", id: "history", href: "/history" }, - - { label: "Settings", id: "settings", href: "/settings" }, -]; diff --git a/src/entities/campaign/index.ts b/src/entities/campaign/index.ts new file mode 100644 index 00000000..e3907636 --- /dev/null +++ b/src/entities/campaign/index.ts @@ -0,0 +1,9 @@ +export * from "./components/SingleCampaignBanner"; +export * from "./components/CampaignCard"; +export * from "./components/CampaignDonorsTable"; +export * from "./components/CampaignForm"; +export * from "./components/CampaignProgressBar"; +export * from "./components/CampaignSettings"; + +export * from "./hooks/redirects"; +export * from "./hooks/useCampaign"; diff --git a/src/entities/list/index.ts b/src/entities/list/index.ts index 4733c03e..6e79cd02 100644 --- a/src/entities/list/index.ts +++ b/src/entities/list/index.ts @@ -1,11 +1,15 @@ export * from "./types"; export * from "./constants"; + export * from "./components/ListHero"; export * from "./components/ListAccounts"; export * from "./components/ListCard"; +export * from "./components/CreateListHero"; export * from "./components/ListDetails"; +export * from "./components/ListFormDetails"; export * from "./components/ListsOverview"; export * from "./components/registration-status"; + export * from "./hooks/redirects"; export { listEditorModel, listModelKey } from "./models"; diff --git a/src/entities/pot/components/PotCard.tsx b/src/entities/pot/components/PotCard.tsx index da53e086..f38b3b68 100644 --- a/src/entities/pot/components/PotCard.tsx +++ b/src/entities/pot/components/PotCard.tsx @@ -7,7 +7,7 @@ import { ByPotId, Pot, indexer } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; import { formatWithCommas } from "@/common/lib"; import { useToken } from "@/entities/_shared/token"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { Indicator } from "./Indicator"; import { PotTag } from "./PotTag"; @@ -61,7 +61,7 @@ export const PotCard: React.FC = ({ potId }) => { ) : ( // Card {/* Card Section */} diff --git a/src/entities/pot/components/PotDonationEntry.tsx b/src/entities/pot/components/PotDonationEntry.tsx index a774b918..f528c020 100644 --- a/src/entities/pot/components/PotDonationEntry.tsx +++ b/src/entities/pot/components/PotDonationEntry.tsx @@ -5,7 +5,7 @@ import { Donation } from "@/common/api/indexer"; import { truncate } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; import { useAccountSocialProfile } from "@/entities/_shared/account"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import NearIcon from "./NearIcon"; import { FundingSrc } from "./styled"; @@ -38,10 +38,10 @@ export const PotDonationEntry = ({ ); const url = projectId - ? `${routesPath.PROFILE}/${donorId}` - : `${routesPath.PROFILE}/${projectId || recipientId}`; + ? `${rootPathnames.PROFILE}/${donorId}` + : `${rootPathnames.PROFILE}/${projectId || recipientId}`; - const recipientUrl = `${routesPath.PROJECT}/${recipientId}`; + const recipientUrl = `${rootPathnames.PROJECT}/${recipientId}`; const name = truncate(donorId, 15); const recipientName = truncate(recipientId, 15); diff --git a/src/entities/pot/components/PotPayoutChallenges.tsx b/src/entities/pot/components/PotPayoutChallenges.tsx index 068d9505..00ad7dab 100644 --- a/src/entities/pot/components/PotPayoutChallenges.tsx +++ b/src/entities/pot/components/PotPayoutChallenges.tsx @@ -9,7 +9,7 @@ import AdminIcon from "@/common/ui/svg/AdminIcon"; import { CheckedIcon } from "@/common/ui/svg/CheckedIcon"; import { cn } from "@/common/ui/utils"; import { AccountProfilePicture } from "@/entities/_shared/account"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { useGlobalStoreSelector } from "@/store"; import ChallengeResolveModal from "./ChallengeResolveModal"; @@ -113,7 +113,7 @@ export const PotPayoutChallenges = ({ className="h-8 w-8 rounded-full" /> {challenger_id} diff --git a/src/entities/pot/components/PotSponsorsTable.tsx b/src/entities/pot/components/PotSponsorsTable.tsx index 9d897199..3291ba78 100644 --- a/src/entities/pot/components/PotSponsorsTable.tsx +++ b/src/entities/pot/components/PotSponsorsTable.tsx @@ -12,7 +12,7 @@ import { TooltipTrigger, } from "@/common/ui/components"; import { AccountProfilePicture } from "@/entities/_shared/account"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { CustomDonationType } from "../models/types"; @@ -159,7 +159,11 @@ export const PotSponsorsTable = ({ sponsors }: { sponsors: CustomDonationType[]
#{idx + 1 + (currentPage - 1) * perPage}
- + {/* Tooltip */} @@ -168,7 +172,7 @@ export const PotSponsorsTable = ({ sponsors }: { sponsors: CustomDonationType[] {truncate(donorId, 25)} diff --git a/src/entities/project/components/ProjectCard.tsx b/src/entities/project/components/ProjectCard.tsx index a334009f..6e2d08ff 100644 --- a/src/entities/project/components/ProjectCard.tsx +++ b/src/entities/project/components/ProjectCard.tsx @@ -12,7 +12,7 @@ import { cn } from "@/common/ui/utils"; import { AccountProfileCover, AccountProfilePicture } from "@/entities/_shared/account"; import { useTokenUsdDisplayValue } from "@/entities/_shared/token"; import { useDonation } from "@/features/donation"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { ProjectCardSkeleton } from "./ProjectCardSkeleton"; @@ -46,7 +46,7 @@ export const ProjectCard = ({ projectId, allowDonate = true, payoutDetails }: Pr const categories = plCategories ? JSON.parse(plCategories) : []; return ( - + {isAccountLoading ? ( ) : ( diff --git a/src/entities/project/components/Team.tsx b/src/entities/project/components/Team.tsx index 2b8f63b0..0ba7b7d2 100644 --- a/src/entities/project/components/Team.tsx +++ b/src/entities/project/components/Team.tsx @@ -6,7 +6,7 @@ import Link from "next/link"; import { NEARSocialUserProfile } from "@/common/contracts/social"; import type { AccountId } from "@/common/types"; import { useAccountSocialProfile } from "@/entities/_shared/account"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; const getProfileTeamMembersData = (profileData?: NEARSocialUserProfile) => { if (!profileData) return []; @@ -50,7 +50,7 @@ const Members = ({ team }: { team?: string[] }) => { diff --git a/src/features/donation/components/DonationSuccess.tsx b/src/features/donation/components/DonationSuccess.tsx index ef16d46c..44dbf797 100644 --- a/src/features/donation/components/DonationSuccess.tsx +++ b/src/features/donation/components/DonationSuccess.tsx @@ -24,7 +24,7 @@ import { import TwitterSvg from "@/common/ui/svg/twitter"; import { AccountProfileLink } from "@/entities/_shared/account"; import { TokenTotalValue, useToken } from "@/entities/_shared/token"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { DonationSummaryBreakdown } from "./breakdowns"; import { DonationSybilWarning } from "./DonationSybilWarning"; @@ -108,7 +108,7 @@ export const DonationSuccess = ({ form, transactionHash, closeModal }: DonationS const tag = `${PROJECT_TWITTER_ACCOUNT}`; - let potlockUrl = `https://alpha.potlock.org${routesPath.PROFILE}/${recipient.id}/donations`; + let potlockUrl = `https://alpha.potlock.org${rootPathnames.PROFILE}/${recipient.id}/donations`; let potlockHomeUrl = "https://alpha.potlock.org"; let text = `🎉 Just supported ${tag} (${PROJECT_TWITTER_ACCOUNT}) through @${PLATFORM_TWITTER_ACCOUNT_ID}! @@ -203,7 +203,7 @@ export const DonationSuccess = ({ form, transactionHash, closeModal }: DonationS ) : ( diff --git a/src/features/pot-configuration/components/PotDeploymentSuccess.tsx b/src/features/pot-configuration/components/PotDeploymentSuccess.tsx index 611fc18b..f00f2142 100644 --- a/src/features/pot-configuration/components/PotDeploymentSuccess.tsx +++ b/src/features/pot-configuration/components/PotDeploymentSuccess.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { Button, DialogDescription } from "@/common/ui/components"; import { ChefHatIcon } from "@/common/ui/svg"; import { PotData } from "@/entities/pot"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; export type PotDeploymentSuccessProps = { onViewPotClick: VoidFunction; @@ -32,7 +32,7 @@ export const PotDeploymentSuccess: React.FC = ({
); diff --git a/src/features/pot-deployment/components/buttons.tsx b/src/features/pot-deployment/components/buttons.tsx new file mode 100644 index 00000000..54eaddd6 --- /dev/null +++ b/src/features/pot-deployment/components/buttons.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState } from "react"; + +import Link from "next/link"; + +import { potFactoryContractClient } from "@/common/contracts/core"; +import { Button } from "@/common/ui/components"; +import { useSession } from "@/entities/_shared/session"; +import { rootPathnames } from "@/pathnames"; + +export const PotDeploymentButton: React.FC = () => { + const viewer = useSession(); + + const [isPotDeploymentAvailable, updatePotDeploymentAvailability] = useState(false); + + // TODO: Replace with SWR hook! + useEffect(() => { + if (viewer.isSignedIn) { + potFactoryContractClient + .isDeploymentAvailable({ + accountId: viewer.accountId, + }) + .then(updatePotDeploymentAvailability); + } + }, [viewer.accountId, viewer.isSignedIn]); + + return ( + isPotDeploymentAvailable && ( + + ) + ); +}; diff --git a/src/features/pot-deployment/index.ts b/src/features/pot-deployment/index.ts new file mode 100644 index 00000000..c281dd43 --- /dev/null +++ b/src/features/pot-deployment/index.ts @@ -0,0 +1 @@ +export * from "./components/buttons"; diff --git a/src/features/profile-setup/components/SuccessfulRegister.tsx b/src/features/profile-setup/components/SuccessfulRegister.tsx index 0d9fe20d..c0fc1260 100644 --- a/src/features/profile-setup/components/SuccessfulRegister.tsx +++ b/src/features/profile-setup/components/SuccessfulRegister.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { Button } from "@/common/ui/components"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { dispatch } from "@/store"; const SuccessfulRegister = ({ @@ -26,10 +26,10 @@ const SuccessfulRegister = ({

You've successfully registered!

)}
- + - + diff --git a/src/features/profile-setup/hooks/useInitProjectState.ts b/src/features/profile-setup/hooks/useInitProjectState.ts index a1e122d2..704b2a5b 100644 --- a/src/features/profile-setup/hooks/useInitProjectState.ts +++ b/src/features/profile-setup/hooks/useInitProjectState.ts @@ -5,7 +5,7 @@ import { naxiosInstance } from "@/common/api/near/client"; import { listsContractClient } from "@/common/contracts/core"; import { useRouteQuery } from "@/common/lib"; import { useWallet } from "@/entities/_shared/session"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { dispatch, useGlobalStoreSelector } from "@/store"; // TODO: Ditch Redux for this use case entirely! @@ -90,7 +90,7 @@ export const useInitProjectState = () => { dispatch.projectEditor.isRegistered(!!register); // Auto set the project to DONE status if it's already registered & this is create project page - if (register && location.pathname.includes(routesPath.CREATE_PROJECT)) { + if (register && location.pathname.includes(rootPathnames.CREATE_PROJECT)) { dispatch.projectEditor.submissionStatus("done"); dispatch.projectEditor.setSubmissionError(""); } diff --git a/src/entities/campaign/components/Tabs.tsx b/src/layout/campaign/components/layout.tsx similarity index 59% rename from src/entities/campaign/components/Tabs.tsx rename to src/layout/campaign/components/layout.tsx index 083c2e79..cd146e65 100644 --- a/src/entities/campaign/components/Tabs.tsx +++ b/src/layout/campaign/components/layout.tsx @@ -1,8 +1,26 @@ +import { useCallback, useState } from "react"; + +import { isClient } from "@wpdas/naxios"; import Link from "next/link"; import { useRouter } from "next/router"; +import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { cn } from "@/common/ui/utils"; +import { SingleCampaignBanner } from "@/entities/campaign"; + +const CAMPAIGN_TAB_ROUTES: TabOption[] = [ + { + label: "Leaderboard", + id: "leaderboard", + href: "/leaderboard", + }, + + // { label: "History", id: "history", href: "/history" }, + + { label: "Settings", id: "settings", href: "/settings" }, +]; type Props = { options: TabOption[]; @@ -68,4 +86,41 @@ const Tabs = ({ options, selectedTab, onSelect, asLink }: Props) => { ); }; -export default Tabs; +type ReactLayoutProps = { + children: React.ReactNode; +}; + +export const CampaignLayout: React.FC = ({ children }) => { + const router = useRouter(); + const pathname = router.pathname; + + const tabs = CAMPAIGN_TAB_ROUTES; + + const [selectedTab, setSelectedTab] = useState( + tabs.find((tab) => pathname.includes(tab.href)) || tabs[0], + ); + + const handleSelectedTab = useCallback( + (tabId: string) => setSelectedTab(tabs.find((tabRoute) => tabRoute.id === tabId)!), + [tabs], + ); + + return !isClient() ? ( + + ) : ( + + +
+ +
+ handleSelectedTab(tabId)} + /> +
{children}
+
+
+ ); +}; diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index ed6277f2..a35691bb 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -1,11 +1,12 @@ import { useState } from "react"; +import { isClient } from "@wpdas/naxios"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; -import useIsClient from "@/common/lib/useIsClient"; +import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; import { cn } from "@/common/ui/utils"; import { SessionAuthButton, useSession } from "@/entities/_shared/session"; import { CartLink } from "@/entities/cart"; @@ -35,10 +36,6 @@ const links = [ const AuthButton = () => { const viewer = useSession(); - // TODO: Investigate if it's really necessary - const isClient = useIsClient(); - if (!isClient) return; - return viewer.isSignedIn ? : ; }; @@ -63,13 +60,12 @@ const MobileMenuButton = ({ onClick }: { onClick: () => void }) => { }; const MobileNav = () => { - const isClient = useIsClient(); const router = useRouter(); return (
); } + +FeedPage.getLayout = function getLayout(page: React.ReactNode) { + return isClient() ? ( + {page} + ) : ( + + ); +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 64917d55..a8c8319b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,7 +7,7 @@ import { cn } from "@/common/ui/utils"; import { useSession } from "@/entities/_shared/session"; import { ProjectCard, ProjectDiscovery } from "@/entities/project"; import { DonateRandomly } from "@/features/donation"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; export const FEATURED_PROJECT_ACCOUNT_IDS = NETWORK === "mainnet" @@ -71,8 +71,8 @@ const WelcomeBanner = () => { diff --git a/src/pages/list/create/index.tsx b/src/pages/list/create/index.tsx index 7d5e5fbf..0f8b10bd 100644 --- a/src/pages/list/create/index.tsx +++ b/src/pages/list/create/index.tsx @@ -1,14 +1,23 @@ import React from "react"; -import { PageWithBanner } from "@/common/ui/components"; -import { CreateListHero } from "@/entities/list/components/CreateListHero"; -import { ListFormDetails } from "@/entities/list/components/ListFormDetails"; +import { isClient } from "@wpdas/naxios"; + +import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function Page() { return ( - + + {!isClient() ? ( + + ) : ( + + + + )} ); } diff --git a/src/pages/list/duplicate/[id].tsx b/src/pages/list/duplicate/[id].tsx index 1ff5db07..fe2b1319 100644 --- a/src/pages/list/duplicate/[id].tsx +++ b/src/pages/list/duplicate/[id].tsx @@ -1,12 +1,21 @@ -import { PageWithBanner } from "@/common/ui/components"; -import { CreateListHero } from "@/entities/list/components/CreateListHero"; -import { ListFormDetails } from "@/entities/list/components/ListFormDetails"; +import { isClient } from "@wpdas/naxios"; + +import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function DuplicateList() { return ( - + + {!isClient() ? ( + + ) : ( + + + + )} ); } diff --git a/src/pages/list/edit/[id]/index.tsx b/src/pages/list/edit/[id]/index.tsx index 92d7b2f0..b6fb3955 100644 --- a/src/pages/list/edit/[id]/index.tsx +++ b/src/pages/list/edit/[id]/index.tsx @@ -1,14 +1,23 @@ import React from "react"; -import { PageWithBanner } from "@/common/ui/components"; -import { CreateListHero } from "@/entities/list/components/CreateListHero"; -import { ListFormDetails } from "@/entities/list/components/ListFormDetails"; +import { isClient } from "@wpdas/naxios"; + +import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function Page() { return ( - + + {!isClient() ? ( + + ) : ( + + + + )} ); } diff --git a/src/pages/pots.tsx b/src/pages/pots.tsx index 31e2f0a5..065e0e66 100644 --- a/src/pages/pots.tsx +++ b/src/pages/pots.tsx @@ -1,28 +1,11 @@ -import { useEffect, useState } from "react"; - +import { isClient } from "@wpdas/naxios"; import Link from "next/link"; -import { potFactoryContractClient } from "@/common/contracts/core"; import { Button, PageWithBanner } from "@/common/ui/components"; -import { useWallet } from "@/entities/_shared/session"; import { ActivePots } from "@/entities/pot"; -import { rootPathnames } from "@/pathnames"; +import { PotDeploymentButton } from "@/features/pot-deployment"; const Banner = () => { - const { wallet } = useWallet(); - - const [isPotDeploymentAvailable, updatePotDeploymentAvailability] = useState(false); - - useEffect(() => { - if (wallet?.accountId) { - potFactoryContractClient - .isDeploymentAvailable({ - accountId: wallet.accountId, - }) - .then(updatePotDeploymentAvailability); - } - }, [wallet?.accountId]); - return (
@@ -31,20 +14,22 @@ const Banner = () => { Donate to Matching Rounds
to Get Your Contributions Amplified. -
- {isPotDeploymentAvailable && ( - - )} - - - + +
diff --git a/src/pages/profile/[userId]/campaigns.tsx b/src/pages/profile/[userId]/campaigns.tsx index 5d6b54c8..b8de829e 100644 --- a/src/pages/profile/[userId]/campaigns.tsx +++ b/src/pages/profile/[userId]/campaigns.tsx @@ -3,8 +3,8 @@ import { ReactElement, useEffect, useState } from "react"; import { useRouter } from "next/router"; import { Campaign, campaignsContractClient } from "@/common/contracts/core"; -import { CampaignCard } from "@/entities/campaign/components"; -import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; +import { CampaignCard } from "@/entities/campaign"; +import { ProfileLayout } from "@/layout/profile/components/layout"; import { NoResults } from "./lists"; diff --git a/src/pages/profile/[userId]/donations.tsx b/src/pages/profile/[userId]/donations.tsx index a9db6506..9b2b024c 100644 --- a/src/pages/profile/[userId]/donations.tsx +++ b/src/pages/profile/[userId]/donations.tsx @@ -6,7 +6,7 @@ import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; import { FundingTable } from "@/layout/profile/_deprecated/FundingTable"; -import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; +import { ProfileLayout } from "@/layout/profile/components/layout"; const DonationsTab = () => { const router = useRouter(); diff --git a/src/pages/profile/[userId]/feed.tsx b/src/pages/profile/[userId]/feed.tsx index ac5da9b0..808ed119 100644 --- a/src/pages/profile/[userId]/feed.tsx +++ b/src/pages/profile/[userId]/feed.tsx @@ -8,7 +8,7 @@ import { fetchAccountFeedPosts } from "@/common/api/near-social"; import { IndexPostResultItem } from "@/common/contracts/social"; import { cn } from "@/common/ui/utils"; import { PostCard, PostEditor } from "@/entities/post"; -import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; +import { ProfileLayout } from "@/layout/profile/components/layout"; const NoResults = () => (
; diff --git a/src/pages/profile/[userId]/home.tsx b/src/pages/profile/[userId]/home.tsx index 398bf1f2..ddfdbf36 100644 --- a/src/pages/profile/[userId]/home.tsx +++ b/src/pages/profile/[userId]/home.tsx @@ -11,7 +11,7 @@ import { useAccountSocialProfile } from "@/entities/_shared/account"; import Team from "@/entities/project/components/Team"; import AboutItem from "@/layout/profile/components/AboutItem"; import Github from "@/layout/profile/components/Github"; -import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; +import { ProfileLayout } from "@/layout/profile/components/layout"; import SmartContract from "@/layout/profile/components/SmartContract"; export default function ProfileHomeTab() { diff --git a/src/pages/profile/[userId]/lists.tsx b/src/pages/profile/[userId]/lists.tsx index cab51df3..673ce309 100644 --- a/src/pages/profile/[userId]/lists.tsx +++ b/src/pages/profile/[userId]/lists.tsx @@ -6,7 +6,7 @@ import { indexer } from "@/common/api/indexer"; import { Label, Switch } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { ListCard, getRandomBackgroundImage } from "@/entities/list"; -import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; +import { ProfileLayout } from "@/layout/profile/components/layout"; export const NoResults = ({ text }: { text: string }) => (
Date: Mon, 20 Jan 2025 01:36:14 +0000 Subject: [PATCH 14/84] wip --- src/common/contexts/wallet-manager.tsx | 47 ----------- src/common/contexts/wallet.tsx | 79 +++++++++++++++++++ src/common/viewer/components.tsx | 19 +++++ .../session.ts => common/viewer/hooks.ts} | 35 +++----- src/common/viewer/index.ts | 3 + .../session => common/viewer}/types.ts | 2 +- .../components/AccountFollowButton.tsx | 20 +++-- .../_shared/session/components/buttons.tsx | 21 ----- .../_shared/session/components/providers.tsx | 63 --------------- src/entities/_shared/session/hooks/wallet.ts | 33 -------- src/entities/_shared/session/index.ts | 7 -- .../token/components/TokenSelector.tsx | 4 +- src/entities/post/components/PostEditor.tsx | 4 +- .../pot/components/ChallengeModal.tsx | 4 +- .../voting-round/components/CandidateRow.tsx | 4 +- .../components/CandidateTable.tsx | 4 +- .../components/VoteWeightBreakdown.tsx | 4 +- src/entities/voting-round/hooks/candidates.ts | 6 +- src/entities/voting-round/hooks/clearance.ts | 4 +- .../donation/components/DonationFlow.tsx | 4 +- .../donation/components/DonationModal.tsx | 4 +- .../components/DonationTokenBalance.tsx | 4 +- .../pot-application/hooks/clearance.ts | 4 +- .../pot-deployment/components/buttons.tsx | 4 +- .../components/ProjectEditor.tsx | 4 +- src/features/profile-setup/hooks/forms.ts | 10 +-- .../hooks/useInitProjectState.ts | 12 +-- .../components/payout-manager.tsx | 4 +- src/layout/campaign/components/layout.tsx | 11 ++- src/layout/components/app-bar.tsx | 33 ++++++-- src/layout/components/user-dropdown.tsx | 4 +- src/layout/pot/components/layout-hero.tsx | 4 +- src/layout/pot/components/layout.tsx | 6 +- .../components/ProfileLayoutControls.tsx | 6 +- src/layout/profile/components/layout.tsx | 6 +- src/pages/campaign/create.tsx | 6 +- src/pages/campaigns.tsx | 11 ++- src/pages/deploy.tsx | 6 +- src/pages/edit-project/[projectId].tsx | 12 +-- src/pages/feed/[account]/[block]/index.tsx | 8 +- src/pages/feed/index.tsx | 8 +- src/pages/index.tsx | 4 +- src/pages/list/create/index.tsx | 6 +- src/pages/list/duplicate/[id].tsx | 6 +- src/pages/list/edit/[id]/index.tsx | 6 +- src/pages/pot/[potId]/votes.tsx | 4 +- src/pages/register.tsx | 13 ++- 47 files changed, 254 insertions(+), 319 deletions(-) delete mode 100644 src/common/contexts/wallet-manager.tsx create mode 100644 src/common/contexts/wallet.tsx create mode 100644 src/common/viewer/components.tsx rename src/{entities/_shared/session/hooks/session.ts => common/viewer/hooks.ts} (57%) create mode 100644 src/common/viewer/index.ts rename src/{entities/_shared/session => common/viewer}/types.ts (95%) delete mode 100644 src/entities/_shared/session/components/buttons.tsx delete mode 100644 src/entities/_shared/session/components/providers.tsx delete mode 100644 src/entities/_shared/session/hooks/wallet.ts delete mode 100644 src/entities/_shared/session/index.ts diff --git a/src/common/contexts/wallet-manager.tsx b/src/common/contexts/wallet-manager.tsx deleted file mode 100644 index 6936ec26..00000000 --- a/src/common/contexts/wallet-manager.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { createContext, useContext, useEffect, useMemo, useState } from "react"; - -import { WalletManager } from "@wpdas/naxios/dist/types/managers/wallet-manager"; - -import { nearClient } from "@/common/api/near"; - -type WalletManagerContextState = - | { isReady: false } - | ({ isReady: true } & Omit); - -const initialWalletContextState: WalletManagerContextState = { isReady: false }; - -export const WalletManagerContext = - createContext(initialWalletContextState); - -export type WalletManagerContextProviderProps = { - children: React.ReactNode; -}; - -export const WalletManagerProvider: React.FC = ({ - children, -}) => { - const [isReady, setIsReady] = useState(false); - - useEffect(() => { - nearClient.walletApi - .initNear() - .then(() => setIsReady(true)) - .catch((error) => { - console.log(error); - setIsReady(false); - }); - }, []); - - const resolvedContext = useMemo( - () => (isReady ? { isReady, ...nearClient.walletApi } : initialWalletContextState), - [isReady], - ); - - return ( - - {children} - - ); -}; - -export const useWalletManagerContext = () => useContext(WalletManagerContext); diff --git a/src/common/contexts/wallet.tsx b/src/common/contexts/wallet.tsx new file mode 100644 index 00000000..b75033d9 --- /dev/null +++ b/src/common/contexts/wallet.tsx @@ -0,0 +1,79 @@ +import { createContext, useCallback, useContext, useEffect, useState } from "react"; + +import { nearClient } from "@/common/api/near"; + +import type { AccountId } from "../types"; + +type WalletContextState = + | { isReady: false; isSignedIn: false; accountId: undefined } + | { isReady: true; isSignedIn: boolean; accountId?: AccountId }; + +const initialWalletContextState: WalletContextState = { + isReady: false, + isSignedIn: false, + accountId: undefined, +}; + +const WalletContext = createContext(initialWalletContextState); + +export type WalletProviderProps = { + children: React.ReactNode; +}; + +export const WalletProvider: React.FC = ({ children }) => { + const [isReady, setIsReady] = useState(false); + const [error, setError] = useState(null); + const [isSignedIn, setIsSignedIn] = useState(false); + const [accountId, setAccountId] = useState(undefined); + + const syncWalletState = useCallback(() => { + setIsSignedIn(nearClient.walletApi.walletSelector.isSignedIn()); + setAccountId(nearClient.walletApi.accountId); + }, []); + + useEffect(() => { + if (!isReady && error !== null) { + nearClient.walletApi + .initNear() + .then(() => setIsReady(true)) + .catch((error) => { + console.log(error); + setError(error); + setIsReady(false); + }); + } + }, [error, isReady]); + + useEffect(() => { + if (isReady) { + nearClient.walletApi.walletSelector.on("signedIn", syncWalletState); + nearClient.walletApi.walletSelector.on("signedOut", syncWalletState); + nearClient.walletApi.walletSelector.on("accountsChanged", syncWalletState); + nearClient.walletApi.walletSelector.on("networkChanged", syncWalletState); + nearClient.walletApi.walletSelector.on("uriChanged", syncWalletState); + } + + return () => { + if (isReady) { + nearClient.walletApi.walletSelector.off("signedIn", syncWalletState); + nearClient.walletApi.walletSelector.off("signedOut", syncWalletState); + nearClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); + nearClient.walletApi.walletSelector.off("networkChanged", syncWalletState); + nearClient.walletApi.walletSelector.off("uriChanged", syncWalletState); + } + }; + }, [syncWalletState, isReady]); + + return ( + + {children} + + ); +}; + +/** + * To be used ONLY for session management! + */ +export const useWalletContext = () => useContext(WalletContext); diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx new file mode 100644 index 00000000..d73fe64f --- /dev/null +++ b/src/common/viewer/components.tsx @@ -0,0 +1,19 @@ +import { useMemo } from "react"; + +import { isClient } from "@wpdas/naxios"; + +import { WalletProvider } from "../contexts/wallet"; + +export type ViewerSessionProviderProps = { + children: React.ReactNode; + fallback: React.ReactNode; +}; + +export const ViewerSessionProvider: React.FC = ({ + children, + fallback, +}) => { + const isClientRender = useMemo(isClient, []); + + return isClientRender ? {children} : fallback; +}; diff --git a/src/entities/_shared/session/hooks/session.ts b/src/common/viewer/hooks.ts similarity index 57% rename from src/entities/_shared/session/hooks/session.ts rename to src/common/viewer/hooks.ts index 8f12b144..ec0acc8e 100644 --- a/src/entities/_shared/session/hooks/session.ts +++ b/src/common/viewer/hooks.ts @@ -3,35 +3,24 @@ import { useMemo } from "react"; import { prop } from "remeda"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { useWalletManagerContext } from "@/common/contexts/wallet-manager"; +import { useWalletContext } from "@/common/contexts/wallet"; import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; import { AccountId } from "@/common/types"; import { useGlobalStoreSelector } from "@/store"; -import { Session } from "../types"; +import { ViewerSession } from "./types"; -// TODO: Subscribe to wallet events to keep isSignedIn synced -export const useSession = (): Session => { - const walletManagerContext = useWalletManagerContext(); +export const useViewerSession = (): ViewerSession => { + const wallet = useWalletContext(); + const { actAsDao } = useGlobalStoreSelector(prop("nav")); + const isDaoRepresentative = actAsDao.toggle && Boolean(actAsDao.defaultAddress); - const isSignedIn = useMemo( - () => (walletManagerContext.isReady ? walletManagerContext.walletSelector.isSignedIn() : false), - [walletManagerContext], - ); - - const walletAccountId = useMemo( - () => (walletManagerContext.isReady ? walletManagerContext.accountId : null), - [walletManagerContext], - ); - - const { actAsDao, accountId: lastActiveAccountId } = useGlobalStoreSelector(prop("nav")); - const asDao = actAsDao.toggle && Boolean(actAsDao.defaultAddress); - - const accountId: AccountId | undefined = useMemo( - () => (asDao ? actAsDao.defaultAddress : (walletAccountId ?? lastActiveAccountId)), - [actAsDao.defaultAddress, asDao, lastActiveAccountId, walletAccountId], - ); + const accountId: AccountId | undefined = useMemo(() => { + if (wallet.isReady) { + return isDaoRepresentative ? actAsDao.defaultAddress : wallet.accountId; + } else return undefined; + }, [actAsDao.defaultAddress, isDaoRepresentative, wallet.accountId, wallet.isReady]); /** * Account for edge cases in which the wallet is connected to a mismatching network @@ -46,7 +35,7 @@ export const useSession = (): Session => { listId: PUBLIC_GOODS_REGISTRY_LIST_ID, }); - if (isSignedIn && accountId && isAccountIdValid) { + if (wallet.isSignedIn && accountId && isAccountIdValid) { return { accountId, isSignedIn: true, diff --git a/src/common/viewer/index.ts b/src/common/viewer/index.ts new file mode 100644 index 00000000..777db88c --- /dev/null +++ b/src/common/viewer/index.ts @@ -0,0 +1,3 @@ +export * from "./components"; +export * from "./hooks"; +export * from "./types"; diff --git a/src/entities/_shared/session/types.ts b/src/common/viewer/types.ts similarity index 95% rename from src/entities/_shared/session/types.ts rename to src/common/viewer/types.ts index dd0a5681..def5706a 100644 --- a/src/entities/_shared/session/types.ts +++ b/src/common/viewer/types.ts @@ -1,7 +1,7 @@ import type { RegistrationStatus } from "@/common/contracts/core/lists"; import { AccountId } from "@/common/types"; -export type Session = +export type ViewerSession = | { accountId: AccountId; registrationStatus?: RegistrationStatus; diff --git a/src/entities/_shared/account/components/AccountFollowButton.tsx b/src/entities/_shared/account/components/AccountFollowButton.tsx index 2e4e1ccd..a8ea1fbe 100644 --- a/src/entities/_shared/account/components/AccountFollowButton.tsx +++ b/src/entities/_shared/account/components/AccountFollowButton.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { socialDbContractClient } from "@/common/contracts/social"; import type { ByAccountId } from "@/common/types"; import { Button } from "@/common/ui/components"; -import { useWallet } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; export type AccountFollowButtonProps = ByAccountId & { className?: string; @@ -13,28 +13,27 @@ export const AccountFollowButton: React.FC = ({ accountId, className, }) => { - const { wallet } = useWallet(); - + const viewer = useViewerSession(); const [followEdge, setFollowEdge] = useState>(); const [inverseEdge, setInverseEdge] = useState>(); useEffect(() => { (async () => { - if (wallet?.accountId) { + if (viewer.accountId) { const _followEdge = await socialDbContractClient.getSocialData>({ - path: `${wallet.accountId}/graph/follow/${accountId}`, + path: `${viewer.accountId}/graph/follow/${accountId}`, }); setFollowEdge(_followEdge); const _inverseEdge = await socialDbContractClient.getSocialData>({ - path: `${accountId}/graph/follow/${wallet.accountId}`, + path: `${accountId}/graph/follow/${viewer.accountId}`, }); setInverseEdge(_inverseEdge); } })(); - }, [wallet?.accountId, accountId]); + }, [viewer.accountId, accountId]); // const loading = followEdge === undefined || inverseEdge === undefined; const [loading, setLoading] = useState(true); @@ -83,17 +82,17 @@ export const AccountFollowButton: React.FC = ({ const [updating, setUpdating] = useState(false); - if (!accountId || !wallet?.accountId || wallet.accountId === accountId) { + if (!accountId || !viewer?.accountId || viewer.accountId === accountId) { return ""; } const onClickHandler = async () => { - if (wallet.accountId && buttonText !== "Following") { + if (viewer.accountId && buttonText !== "Following") { setUpdating(true); await socialDbContractClient.setSocialData({ data: { - [wallet.accountId]: data, + [viewer.accountId]: data, }, }); @@ -106,7 +105,6 @@ export const AccountFollowButton: React.FC = ({ - ); -}; diff --git a/src/entities/_shared/session/components/providers.tsx b/src/entities/_shared/session/components/providers.tsx deleted file mode 100644 index fd7a3212..00000000 --- a/src/entities/_shared/session/components/providers.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; - -import { isClient } from "@wpdas/naxios"; - -import { walletApi } from "@/common/api/near/client"; -import { SplashScreen } from "@/common/ui/components"; - -import { useWallet } from "../hooks/wallet"; - -type SessionProviderProps = { - children: React.ReactNode; -}; - -export const SessionProvider = ({ children }: SessionProviderProps) => { - const [isReady, setReady] = useState(false); - const [isAuthenticated, setIsAuthenticated] = useState(false); - const { wallet } = useWallet(); - - // Check wallet - const checkWallet = useCallback(async () => { - if (wallet) { - // Starts the wallet manager - if (!isReady) { - wallet.initNear().then(() => setReady(true)); - } - - const isSignedIn = walletApi.walletSelector.isSignedIn(); - - if (isSignedIn !== isAuthenticated) { - setIsAuthenticated(isSignedIn); - } - } - }, [isAuthenticated, isReady, wallet]); - - // Re-init when user is signed in - useEffect(() => { - if (wallet) { - // Initial wallet state - checkWallet(); - - // On sign in wallet state - walletApi.walletSelector.on("signedIn", checkWallet); - - // On sign out wallet state - walletApi.walletSelector.on("signedOut", checkWallet); - } - - return () => { - if (wallet) { - wallet.walletSelector.off("signedIn", checkWallet); - wallet.walletSelector.off("signedOut", checkWallet); - } - }; - }, [checkWallet, wallet]); - - return isClient() && - //! MyNearWallet refuses to work upon relogin without this hack - isReady ? ( - <>{children} - ) : ( - - ); -}; diff --git a/src/entities/_shared/session/hooks/wallet.ts b/src/entities/_shared/session/hooks/wallet.ts deleted file mode 100644 index 2b4fb548..00000000 --- a/src/entities/_shared/session/hooks/wallet.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { useEffect, useState } from "react"; - -import { WalletManager } from "@wpdas/naxios/dist/types/managers/wallet-manager"; - -import { walletApi } from "@/common/api/near/client"; - -export type Wallet = Omit; - -export const useWallet = () => { - const [isWalletReady, setReady] = useState(false); - const [isSignedIn, setIsSignedIn] = useState(false); - const [wallet, setWallet] = useState(); - - useEffect(() => { - (async () => { - // Starts the wallet manager - await walletApi.initNear(); - - const isSignedIn = walletApi.walletSelector.isSignedIn(); - setIsSignedIn(isSignedIn); - setWallet(walletApi); - setReady(true); - })(); - }, []); - - return { - isWalletReady, - isSignedIn, - wallet, - }; -}; - -export type WalletContext = {}; diff --git a/src/entities/_shared/session/index.ts b/src/entities/_shared/session/index.ts deleted file mode 100644 index 73f3ccbe..00000000 --- a/src/entities/_shared/session/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from "./types"; - -export * from "./components/buttons"; -export * from "./components/providers"; - -export * from "./hooks/session"; -export * from "./hooks/wallet"; diff --git a/src/entities/_shared/token/components/TokenSelector.tsx b/src/entities/_shared/token/components/TokenSelector.tsx index c8c33c1f..238351c7 100644 --- a/src/entities/_shared/token/components/TokenSelector.tsx +++ b/src/entities/_shared/token/components/TokenSelector.tsx @@ -1,12 +1,12 @@ import { NATIVE_TOKEN_ID } from "@/common/constants"; import type { ByTokenId } from "@/common/types"; import { SelectField, SelectFieldOption, SelectFieldProps } from "@/common/ui/form-fields"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useToken, useTokenAllowlist } from "../hooks"; const TokenSelectorOption: React.FC = ({ tokenId }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const { data: token } = useToken({ tokenId, diff --git a/src/entities/post/components/PostEditor.tsx b/src/entities/post/components/PostEditor.tsx index 41facfe9..96f8adb7 100644 --- a/src/entities/post/components/PostEditor.tsx +++ b/src/entities/post/components/PostEditor.tsx @@ -5,11 +5,11 @@ import { LazyLoadImage } from "react-lazy-load-image-component"; import { socialDbContractClient } from "@/common/contracts/social"; import { AccountId } from "@/common/types"; import { Button, Textarea } from "@/common/ui/components"; +import { useViewerSession } from "@/common/viewer"; import { useAccountSocialProfile } from "@/entities/_shared/account"; -import { useSession } from "@/entities/_shared/session"; export const PostEditor = ({ accountId }: { accountId: AccountId }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const { avatarSrc } = useAccountSocialProfile({ enabled: viewer.isSignedIn, diff --git a/src/entities/pot/components/ChallengeModal.tsx b/src/entities/pot/components/ChallengeModal.tsx index d6e3c547..a26d9680 100644 --- a/src/entities/pot/components/ChallengeModal.tsx +++ b/src/entities/pot/components/ChallengeModal.tsx @@ -14,7 +14,7 @@ import { Spinner, Textarea, } from "@/common/ui/components"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useChallengeForm } from "../hooks/forms"; @@ -30,7 +30,7 @@ export const ChallengeModal: React.FC = ({ potId, potDetail, }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const { data: potPayoutChallenges } = potContractHooks.usePayoutChallenges({ potId }); const activeChallenge = useMemo(() => { diff --git a/src/entities/voting-round/components/CandidateRow.tsx b/src/entities/voting-round/components/CandidateRow.tsx index 3e36bd55..106a677e 100644 --- a/src/entities/voting-round/components/CandidateRow.tsx +++ b/src/entities/voting-round/components/CandidateRow.tsx @@ -6,8 +6,8 @@ import { Dot } from "lucide-react"; import { ByElectionId, Candidate } from "@/common/contracts/core/voting"; import { Button, Checkbox, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; +import { useViewerSession } from "@/common/viewer"; import { AccountListItem } from "@/entities/_shared/account"; -import { useSession } from "@/entities/_shared/session"; import { useVotingRoundCandidateEntry } from "../hooks/candidates"; @@ -25,7 +25,7 @@ export const VotingRoundCandidateRow: React.FC = ( isSelected = false, onSelect, }) => { - const user = useSession(); + const user = useViewerSession(); const { isLoading, canReceiveVotes, hasUserVotes, handleVoteCast } = useVotingRoundCandidateEntry( { electionId, accountId }, diff --git a/src/entities/voting-round/components/CandidateTable.tsx b/src/entities/voting-round/components/CandidateTable.tsx index 49c33da6..70bc263a 100644 --- a/src/entities/voting-round/components/CandidateTable.tsx +++ b/src/entities/voting-round/components/CandidateTable.tsx @@ -13,7 +13,7 @@ import { AccountId } from "@/common/types"; import { Button, ScrollArea } from "@/common/ui/components"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { VotingRoundCandidateRow } from "./CandidateRow"; @@ -30,7 +30,7 @@ export const VotingRoundCandidateTable: React.FC }) => { const { height: windowHeight } = useWindowSize(); const { toast } = useToast(); - const viewer = useSession(); + const viewer = useViewerSession(); const selectedEntries = useSet(); const { data: isVotingPeriodOngoing } = votingContractHooks.useIsVotingPeriod({ diff --git a/src/entities/voting-round/components/VoteWeightBreakdown.tsx b/src/entities/voting-round/components/VoteWeightBreakdown.tsx index 3de67731..4ab3712b 100644 --- a/src/entities/voting-round/components/VoteWeightBreakdown.tsx +++ b/src/entities/voting-round/components/VoteWeightBreakdown.tsx @@ -12,7 +12,7 @@ import { Separator, } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useVotingRoundVoterVoteWeight, @@ -34,7 +34,7 @@ export const VotingRoundVoteWeightBreakdown: React.FC { const isDialogOpen = useMemo(() => open && mode === "modal", [mode, open]); - const { accountId } = useSession(); + const { accountId } = useViewerSession(); const { voteWeight } = useVotingRoundVoterVoteWeight({ accountId, potId }); const voteWeightAmplifiers = useVotingRoundVoterVoteWeightAmplifiers({ accountId, potId }); diff --git a/src/entities/voting-round/hooks/candidates.ts b/src/entities/voting-round/hooks/candidates.ts index 45937f70..997805da 100644 --- a/src/entities/voting-round/hooks/candidates.ts +++ b/src/entities/voting-round/hooks/candidates.ts @@ -9,13 +9,13 @@ import { import { isAccountId } from "@/common/lib"; import { type AccountId, ByAccountId } from "@/common/types"; import { useToast } from "@/common/ui/hooks"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; export interface VotingRoundCandidateLookup extends ByElectionId {} export const useVotingRoundCandidateLookup = ({ electionId }: VotingRoundCandidateLookup) => { const [searchTerm, setSearchTerm] = useState(""); - const viewer = useSession(); + const viewer = useViewerSession(); const { data, ...candidatesQueryResult } = votingContractHooks.useElectionCandidates({ enabled: electionId !== 0, @@ -67,7 +67,7 @@ export const useVotingRoundCandidateEntry = ({ accountId, }: ByElectionId & ByAccountId) => { const { toast } = useToast(); - const viewer = useSession(); + const viewer = useViewerSession(); const { data: isVotingPeriodOngoing = false } = votingContractHooks.useIsVotingPeriod({ enabled: electionId !== 0, diff --git a/src/entities/voting-round/hooks/clearance.ts b/src/entities/voting-round/hooks/clearance.ts index 26ce2465..e2afed8c 100644 --- a/src/entities/voting-round/hooks/clearance.ts +++ b/src/entities/voting-round/hooks/clearance.ts @@ -4,7 +4,7 @@ import { prop } from "remeda"; import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { ClearanceCheckResult } from "@/common/types"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; // TODO: refactor to support multi-mechanism for the V2 milestone /** @@ -12,7 +12,7 @@ import { useSession } from "@/entities/_shared/session"; * as it's built for the mpDAO milestone. */ export const useVotingRoundSessionClearance = (): ClearanceCheckResult => { - const { accountId, hasRegistrationApproved } = useSession(); + const { accountId, hasRegistrationApproved } = useViewerSession(); const { isHumanVerified: isHuman } = useIsHuman(accountId); return useMemo(() => { diff --git a/src/features/donation/components/DonationFlow.tsx b/src/features/donation/components/DonationFlow.tsx index 147a973f..2d84942a 100644 --- a/src/features/donation/components/DonationFlow.tsx +++ b/src/features/donation/components/DonationFlow.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { isBigSource, useRouteQuery } from "@/common/lib"; import { Button, DialogFooter, Form, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useToken } from "@/entities/_shared/token"; import { dispatch } from "@/store"; @@ -25,7 +25,7 @@ export const DonationFlow: React.FC = ({ closeModal, ...props }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const { currentStep, finalOutcome } = useDonationState(); const { diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index 8a49d1db..6d76fb7e 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -6,7 +6,7 @@ import { walletApi } from "@/common/api/near/client"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { dispatch } from "@/store"; import { DonationFlow, DonationFlowProps } from "./DonationFlow"; @@ -23,7 +23,7 @@ export const DonationModal = create((props: DonationModalProps) => { const isListDonation = "listId" in props; const isCampaignDonation = "campaignId" in props; const { currentStep } = useDonationState(); - const viewer = useSession(); + const viewer = useViewerSession(); const { setSearchParams } = useRouteQuery(); const close = useCallback(() => { diff --git a/src/features/donation/components/DonationTokenBalance.tsx b/src/features/donation/components/DonationTokenBalance.tsx index 35a0e97c..f9de6346 100644 --- a/src/features/donation/components/DonationTokenBalance.tsx +++ b/src/features/donation/components/DonationTokenBalance.tsx @@ -1,7 +1,7 @@ import { ByTokenId } from "@/common/types"; import { Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useToken } from "@/entities/_shared/token"; export type DonationTokenBalanceProps = ByTokenId & { @@ -12,7 +12,7 @@ export const DonationTokenBalance: React.FC = ({ tokenId, classNames, }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const { data: token, error: tokenError } = useToken({ balanceCheckAccountId: viewer?.accountId, diff --git a/src/features/pot-application/hooks/clearance.ts b/src/features/pot-application/hooks/clearance.ts index 621932e9..99dbbcb4 100644 --- a/src/features/pot-application/hooks/clearance.ts +++ b/src/features/pot-application/hooks/clearance.ts @@ -7,7 +7,7 @@ import { ByPotId, indexer } from "@/common/api/indexer"; import { METAPOOL_MPDAO_VOTING_POWER_DECIMALS } from "@/common/contracts/metapool"; import { indivisibleUnitsToBigNum } from "@/common/lib"; import { type AccountId, ClearanceCheckResult } from "@/common/types"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useToken } from "@/entities/_shared/token"; import { POT_APPLICATION_REQUIREMENTS_MPDAO } from "../constants"; @@ -24,7 +24,7 @@ export const usePotApplicationUserClearance = ({ const { staking } = POT_APPLICATION_REQUIREMENTS_MPDAO; const { data: pot } = indexer.usePot({ potId }); - const viewer = useSession(); + const viewer = useViewerSession(); const { data: voterInfo } = indexer.useMpdaoVoter({ enabled: viewer.isSignedIn, diff --git a/src/features/pot-deployment/components/buttons.tsx b/src/features/pot-deployment/components/buttons.tsx index 54eaddd6..6635f1b8 100644 --- a/src/features/pot-deployment/components/buttons.tsx +++ b/src/features/pot-deployment/components/buttons.tsx @@ -4,11 +4,11 @@ import Link from "next/link"; import { potFactoryContractClient } from "@/common/contracts/core"; import { Button } from "@/common/ui/components"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { rootPathnames } from "@/pathnames"; export const PotDeploymentButton: React.FC = () => { - const viewer = useSession(); + const viewer = useViewerSession(); const [isPotDeploymentAvailable, updatePotDeploymentAvailability] = useState(false); diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index fbc9c341..9c15ada7 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -6,7 +6,7 @@ import { prop } from "remeda"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; -import { useSession, useWallet } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { rootPathnames } from "@/pathnames"; import { dispatch, useGlobalStoreSelector } from "@/store"; @@ -41,7 +41,7 @@ export const ProjectEditor = () => { typeof projectIdPathParam === "string" ? projectIdPathParam : projectIdPathParam?.at(0); const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); - const viewer = useSession(); + const viewer = useViewerSession(); const { form, errors, onSubmit } = useProjectEditorForm(); const values = form.watch(); diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 443b96b8..ae957829 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -3,7 +3,7 @@ import { useCallback } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { SubmitHandler, useForm } from "react-hook-form"; -import { useWallet } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { dispatch } from "@/store"; import { saveProject } from "../models/effects"; @@ -11,17 +11,17 @@ import { addFundingSourceSchema, projectEditorSchema } from "../models/schemas"; import { AddFundingSourceInputs, ProjectEditorInputs } from "../models/types"; export const useProjectEditorForm = () => { + const viewer = useViewerSession(); + const form = useForm({ resolver: zodResolver(projectEditorSchema), mode: "onChange", }); - const { wallet } = useWallet(); - const onSubmit: SubmitHandler = useCallback( async (_) => { // not using form data, using store data provided by form - if (wallet) { + if (viewer.isSignedIn) { dispatch.projectEditor.submissionStatus("sending"); saveProject().then(async (result) => { @@ -33,7 +33,7 @@ export const useProjectEditorForm = () => { }); } }, - [wallet], + [viewer], ); return { diff --git a/src/features/profile-setup/hooks/useInitProjectState.ts b/src/features/profile-setup/hooks/useInitProjectState.ts index 704b2a5b..051b58d3 100644 --- a/src/features/profile-setup/hooks/useInitProjectState.ts +++ b/src/features/profile-setup/hooks/useInitProjectState.ts @@ -4,7 +4,7 @@ import { LISTS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; import { naxiosInstance } from "@/common/api/near/client"; import { listsContractClient } from "@/common/contracts/core"; import { useRouteQuery } from "@/common/lib"; -import { useWallet } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { rootPathnames } from "@/pathnames"; import { dispatch, useGlobalStoreSelector } from "@/store"; @@ -25,7 +25,7 @@ export const useInitProjectState = () => { actAsDao: { defaultAddress: daoAddress, toggle: isDao }, } = useGlobalStoreSelector((state) => state.nav); - const { wallet, isWalletReady } = useWallet(); + const viewer = useViewerSession(); // Reset statuses useEffect(() => { @@ -39,16 +39,16 @@ export const useInitProjectState = () => { // Set current accountId to the state useEffect(() => { // Project's id - if (isWalletReady) { + if (viewer.isSignedIn) { if (isDao && daoAddress) { dispatch.projectEditor.setAccountId(daoAddress); dispatch.projectEditor.setIsDao(isDao); dispatch.projectEditor.setDaoAddress(daoAddress); - } else if (wallet?.accountId) { - dispatch.projectEditor.setAccountId(projectId || wallet.accountId); + } else { + dispatch.projectEditor.setAccountId(projectId || viewer.accountId); } } - }, [accountId, isDao, daoAddress, wallet?.accountId, isWalletReady, projectId]); + }, [accountId, isDao, daoAddress, projectId, viewer.isSignedIn, viewer.accountId]); // Set initial loaded project data // const [initialDataLoaded, setInitialDataLoaded] = useState(false); diff --git a/src/features/proportional-funding/components/payout-manager.tsx b/src/features/proportional-funding/components/payout-manager.tsx index d7b6b214..069cfb56 100644 --- a/src/features/proportional-funding/components/payout-manager.tsx +++ b/src/features/proportional-funding/components/payout-manager.tsx @@ -6,7 +6,7 @@ import type { ByPotId } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; import { Button, Skeleton } from "@/common/ui/components"; import { useToast } from "@/common/ui/hooks"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { useToken } from "@/entities/_shared/token"; import { usePotAuthorization } from "@/entities/pot"; import { VotingRoundResultsTable, useVotingRoundResults } from "@/entities/voting-round"; @@ -22,7 +22,7 @@ export const ProportionalFundingPayoutManager: React.FC { const { toast } = useToast(); - const viewer = useSession(); + const viewer = useViewerSession(); const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); const votingRoundResults = useVotingRoundResults({ potId }); diff --git a/src/layout/campaign/components/layout.tsx b/src/layout/campaign/components/layout.tsx index cd146e65..481b902d 100644 --- a/src/layout/campaign/components/layout.tsx +++ b/src/layout/campaign/components/layout.tsx @@ -4,10 +4,11 @@ import { isClient } from "@wpdas/naxios"; import Link from "next/link"; import { useRouter } from "next/router"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { cn } from "@/common/ui/utils"; +import { ViewerSessionProvider } from "@/common/viewer"; import { SingleCampaignBanner } from "@/entities/campaign"; const CAMPAIGN_TAB_ROUTES: TabOption[] = [ @@ -105,10 +106,8 @@ export const CampaignLayout: React.FC = ({ children }) => { [tabs], ); - return !isClient() ? ( - - ) : ( - + return ( + }>
@@ -121,6 +120,6 @@ export const CampaignLayout: React.FC = ({ children }) => { />
{children}
- + ); }; diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index a35691bb..472259f8 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useCallback, useState } from "react"; import { isClient } from "@wpdas/naxios"; import Image from "next/image"; @@ -6,9 +6,11 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { nearClient } from "@/common/api/near"; +import { WalletProvider } from "@/common/contexts/wallet"; +import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { SessionAuthButton, useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { CartLink } from "@/entities/cart"; import { rootPathnames } from "@/pathnames"; @@ -34,9 +36,24 @@ const links = [ ]; const AuthButton = () => { - const viewer = useSession(); - - return viewer.isSignedIn ? : ; + const viewer = useViewerSession(); + + const onSignInClick = useCallback(() => { + nearClient.walletApi.signInModal(); + }, []); + + return viewer.isSignedIn ? ( + + ) : ( + + ); }; const MobileMenuButton = ({ onClick }: { onClick: () => void }) => { @@ -152,9 +169,9 @@ export const AppBar = () => { {isClient() && ( - + - + )} setShowMobileMenu(!showMobileMenu)} /> diff --git a/src/layout/components/user-dropdown.tsx b/src/layout/components/user-dropdown.tsx index 728b51af..a75c6b7d 100644 --- a/src/layout/components/user-dropdown.tsx +++ b/src/layout/components/user-dropdown.tsx @@ -15,15 +15,15 @@ import { DropdownMenuTrigger, Skeleton, } from "@/common/ui/components"; +import { useViewerSession } from "@/common/viewer"; import { AccountProfilePicture, useAccountSocialProfile } from "@/entities/_shared/account"; -import { useSession } from "@/entities/_shared/session"; import { listRegistrationStatuses } from "@/entities/list"; import { rootPathnames } from "@/pathnames"; import { DaoAuth } from "./dao-auth"; export const UserDropdown = () => { - const viewer = useSession(); + const viewer = useViewerSession(); const { profile } = useAccountSocialProfile({ enabled: viewer.isSignedIn, diff --git a/src/layout/pot/components/layout-hero.tsx b/src/layout/pot/components/layout-hero.tsx index dab9f736..c5f7d4af 100644 --- a/src/layout/pot/components/layout-hero.tsx +++ b/src/layout/pot/components/layout-hero.tsx @@ -11,7 +11,7 @@ import { potContractHooks } from "@/common/contracts/core"; import { Button, Checklist, ClipboardCopyButton, Skeleton } from "@/common/ui/components"; import { VolunteerIcon } from "@/common/ui/svg"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { TokenTotalValue } from "@/entities/_shared/token"; import { PotDonationStats, @@ -37,7 +37,7 @@ export const PotLayoutHero: React.FC = ({ onChallengePayoutsClick, onFundMatchingPoolClick, }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); const { data: pot } = indexer.usePot({ potId }); const { data: potPayoutChallenges } = potContractHooks.usePayoutChallenges({ potId }); diff --git a/src/layout/pot/components/layout.tsx b/src/layout/pot/components/layout.tsx index aa409b76..9fffdd47 100644 --- a/src/layout/pot/components/layout.tsx +++ b/src/layout/pot/components/layout.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { ChallengeModal } from "@/entities/pot"; @@ -41,7 +41,7 @@ export const PotLayout: React.FC = ({ children }) => { return !isClient() ? ( ) : ( - + {/** * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE @@ -129,6 +129,6 @@ export const PotLayout: React.FC = ({ children }) => { {/* Tab Content */}
{children}
-
+ ); }; diff --git a/src/layout/profile/components/ProfileLayoutControls.tsx b/src/layout/profile/components/ProfileLayoutControls.tsx index c4d68bec..2370638c 100644 --- a/src/layout/profile/components/ProfileLayoutControls.tsx +++ b/src/layout/profile/components/ProfileLayoutControls.tsx @@ -9,13 +9,13 @@ import { truncate } from "@/common/lib"; import { Button, ClipboardCopyButton } from "@/common/ui/components"; import CheckIcon from "@/common/ui/svg/CheckIcon"; import ReferrerIcon from "@/common/ui/svg/ReferrerIcon"; +import { useViewerSession } from "@/common/viewer"; import { AccountFollowButton, AccountProfileLinktree, AccountProfileTags, useAccountSocialProfile, } from "@/entities/_shared/account"; -import { useSession } from "@/entities/_shared/session"; import { useDonation } from "@/features/donation"; import { rootPathnames } from "@/pathnames"; @@ -25,7 +25,7 @@ type Props = { }; const LinksWrapper = ({ accountId }: { accountId: string }) => { - const viewer = useSession(); + const viewer = useViewerSession(); const [copied, setCopied] = useState(false); return ( @@ -144,7 +144,7 @@ const DonationsInfo = ({ accountId }: { accountId: string }) => { }; export const ProfileLayoutControls = ({ accountId, isProject }: Props) => { - const viewer = useSession(); + const viewer = useViewerSession(); const isOwner = viewer?.accountId === accountId; const { profile } = useAccountSocialProfile({ accountId }); diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index e8f0d236..45fe9640 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { useRegistration } from "@/common/_deprecated/useRegistration"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { ProjectBanner } from "@/entities/project"; @@ -155,7 +155,7 @@ export const ProfileLayout: React.FC = ({ children }) => { return !isClient() ? ( ) : ( - + {isProject && } @@ -175,6 +175,6 @@ export const ProfileLayout: React.FC = ({ children }) => { {children}
-
+ ); }; diff --git a/src/pages/campaign/create.tsx b/src/pages/campaign/create.tsx index d1671f7e..ec91e873 100644 --- a/src/pages/campaign/create.tsx +++ b/src/pages/campaign/create.tsx @@ -1,6 +1,6 @@ import { isClient } from "@wpdas/naxios"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { CampaignForm, useCampaignDeploymentRedirect } from "@/entities/campaign"; @@ -24,9 +24,9 @@ export default function CreateCampaign() { {!isClient() ? ( ) : ( - + - + )} ); diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index 409238cc..873a4c5e 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -1,12 +1,15 @@ import { useEffect, useState } from "react"; +import { isClient } from "@wpdas/naxios"; + +import { WalletProvider } from "@/common/contexts/wallet"; import { Campaign, campaignsContractClient } from "@/common/contracts/core"; -import { PageWithBanner } from "@/common/ui/components"; +import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { CampaignBanner } from "@/entities/campaign/components/CampaignBanner"; import { CampaignsList } from "@/entities/campaign/components/CampaignsList"; import { FeaturedCampaigns } from "@/entities/campaign/components/FeaturedCampaigns"; -export default function Campaigns() { +export default function CampaignsPage() { const [campaigns, setCampaigns] = useState([]); useEffect(() => { @@ -28,3 +31,7 @@ export default function Campaigns() { ); } + +CampaignsPage.getLayout = function getLayout(page: React.ReactNode) { + return isClient() ? {page} : ; +}; diff --git a/src/pages/deploy.tsx b/src/pages/deploy.tsx index 269b1f78..60603a78 100644 --- a/src/pages/deploy.tsx +++ b/src/pages/deploy.tsx @@ -1,6 +1,6 @@ import { isClient } from "@wpdas/naxios"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import InfoIcon from "@/common/ui/svg/InfoIcon"; import { cn } from "@/common/ui/utils"; @@ -15,7 +15,7 @@ export default function PotDeployPage() { return !isClient() ? ( ) : ( - +
- + ); } diff --git a/src/pages/edit-project/[projectId].tsx b/src/pages/edit-project/[projectId].tsx index 1475b4dd..610d7968 100644 --- a/src/pages/edit-project/[projectId].tsx +++ b/src/pages/edit-project/[projectId].tsx @@ -1,14 +1,14 @@ import { isClient } from "@wpdas/naxios"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SpinnerOverlay, SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; export default function EditProjectPage() { - const viewer = useSession(); + const viewer = useViewerSession(); useInitProjectState(); // state used to show spinner during the data post @@ -45,9 +45,5 @@ export default function EditProjectPage() { } EditProjectPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? ( - {page} - ) : ( - - ); + return isClient() ? {page} : ; }; diff --git a/src/pages/feed/[account]/[block]/index.tsx b/src/pages/feed/[account]/[block]/index.tsx index d070fb1f..fab3286d 100644 --- a/src/pages/feed/[account]/[block]/index.tsx +++ b/src/pages/feed/[account]/[block]/index.tsx @@ -8,7 +8,7 @@ import remarkGfm from "remark-gfm"; import { fetchSinglePost, fetchTimeByBlockHeight } from "@/common/api/near-social"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { SplashScreen } from "@/common/ui/components"; import { AccountProfilePicture } from "@/entities/_shared/account"; @@ -113,9 +113,5 @@ export default function FeedAccountBlockPostPage() { } FeedAccountBlockPostPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? ( - {page} - ) : ( - - ); + return isClient() ? {page} : ; }; diff --git a/src/pages/feed/index.tsx b/src/pages/feed/index.tsx index f4a9a8f2..3f71c89a 100644 --- a/src/pages/feed/index.tsx +++ b/src/pages/feed/index.tsx @@ -6,7 +6,7 @@ import InfiniteScrollWrapper from "react-infinite-scroll-component"; import { indexer } from "@/common/api/indexer"; import { fetchGlobalFeeds } from "@/common/api/near-social"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { PostCard } from "@/entities/post"; @@ -113,9 +113,5 @@ export default function FeedPage() { } FeedPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? ( - {page} - ) : ( - - ); + return isClient() ? {page} : ; }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a8c8319b..6cb2f97f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,7 +4,7 @@ import { NETWORK } from "@/common/_config"; import { indexer } from "@/common/api/indexer"; import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { ProjectCard, ProjectDiscovery } from "@/entities/project"; import { DonateRandomly } from "@/features/donation"; import { rootPathnames } from "@/pathnames"; @@ -44,7 +44,7 @@ export const GeneralStats = () => { }; const WelcomeBanner = () => { - const viewer = useSession(); + const viewer = useViewerSession(); return (
) : ( - + - + )} ); diff --git a/src/pages/list/duplicate/[id].tsx b/src/pages/list/duplicate/[id].tsx index fe2b1319..67d52f52 100644 --- a/src/pages/list/duplicate/[id].tsx +++ b/src/pages/list/duplicate/[id].tsx @@ -1,6 +1,6 @@ import { isClient } from "@wpdas/naxios"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { CreateListHero, ListFormDetails } from "@/entities/list"; @@ -12,9 +12,9 @@ export default function DuplicateList() { {!isClient() ? ( ) : ( - + - + )} ); diff --git a/src/pages/list/edit/[id]/index.tsx b/src/pages/list/edit/[id]/index.tsx index b6fb3955..4dc7382b 100644 --- a/src/pages/list/edit/[id]/index.tsx +++ b/src/pages/list/edit/[id]/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import { isClient } from "@wpdas/naxios"; -import { WalletManagerProvider } from "@/common/contexts/wallet-manager"; +import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { CreateListHero, ListFormDetails } from "@/entities/list"; @@ -14,9 +14,9 @@ export default function Page() { {!isClient() ? ( ) : ( - + - + )} ); diff --git a/src/pages/pot/[potId]/votes.tsx b/src/pages/pot/[potId]/votes.tsx index aeafd695..a327e1ad 100644 --- a/src/pages/pot/[potId]/votes.tsx +++ b/src/pages/pot/[potId]/votes.tsx @@ -25,7 +25,7 @@ import { } from "@/common/ui/components"; import { useMediaQuery } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { VotingRoundCandidateFilter, VotingRoundCandidateTable, @@ -39,7 +39,7 @@ import { import { PotLayout } from "@/layout/pot/components/layout"; export default function PotVotesTab() { - const viewer = useSession(); + const viewer = useViewerSession(); const { query: routeQuery } = useRouter(); const { potId } = routeQuery as { potId: string }; const isDesktop = useMediaQuery("(min-width: 1024px)"); diff --git a/src/pages/register.tsx b/src/pages/register.tsx index eb69dd3f..a99b0391 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,11 +1,14 @@ -import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; +import { isClient } from "@wpdas/naxios"; + +import { WalletProvider } from "@/common/contexts/wallet"; +import { PageWithBanner, SpinnerOverlay, SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; +import { useViewerSession } from "@/common/viewer"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; export default function RegisterPage() { - const viewer = useSession(); + const viewer = useViewerSession(); useInitProjectState(); // state used to show spinner during the data post @@ -40,3 +43,7 @@ export default function RegisterPage() { ); } + +RegisterPage.getLayout = function getLayout(page: React.ReactNode) { + return isClient() ? {page} : ; +}; From 6efcfdc0dced406d2285b280788cdffd2679612b Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Mon, 20 Jan 2025 02:11:50 +0000 Subject: [PATCH 15/84] wip --- src/common/viewer/components.tsx | 14 +++++++----- src/common/viewer/hooks.ts | 10 ++++++--- src/common/viewer/internal/wallet-context.ts | 15 +++++++++++++ .../internal/wallet-provider.tsx} | 22 +++---------------- src/layout/campaign/components/layout.tsx | 4 +--- src/layout/components/app-bar.tsx | 2 +- src/layout/pot/components/layout.tsx | 11 ++++------ src/layout/profile/components/layout.tsx | 11 ++++------ src/pages/campaign/create.tsx | 14 ++++-------- src/pages/campaigns.tsx | 10 +++++---- src/pages/deploy.tsx | 12 ++++------ src/pages/edit-project/[projectId].tsx | 11 +++++----- src/pages/feed/[account]/[block]/index.tsx | 9 +++++--- src/pages/feed/index.tsx | 9 +++++--- src/pages/list/create/index.tsx | 16 ++++---------- src/pages/list/duplicate/[id].tsx | 14 ++++-------- src/pages/list/edit/[id]/index.tsx | 14 ++++-------- src/pages/register.tsx | 11 +++++----- 18 files changed, 94 insertions(+), 115 deletions(-) create mode 100644 src/common/viewer/internal/wallet-context.ts rename src/common/{contexts/wallet.tsx => viewer/internal/wallet-provider.tsx} (76%) diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx index d73fe64f..a7f0a554 100644 --- a/src/common/viewer/components.tsx +++ b/src/common/viewer/components.tsx @@ -2,18 +2,22 @@ import { useMemo } from "react"; import { isClient } from "@wpdas/naxios"; -import { WalletProvider } from "../contexts/wallet"; +import { WalletProvider } from "./internal/wallet-provider"; export type ViewerSessionProviderProps = { children: React.ReactNode; - fallback: React.ReactNode; + ssrFallback: React.ReactNode; }; +/** + * Required for session bindings to be available on the client. + * On the server, the ssrFallback is rendered instead. + */ export const ViewerSessionProvider: React.FC = ({ children, - fallback, + ssrFallback, }) => { - const isClientRender = useMemo(isClient, []); + const isCsr = useMemo(isClient, []); - return isClientRender ? {children} : fallback; + return isCsr ? {children} : ssrFallback; }; diff --git a/src/common/viewer/hooks.ts b/src/common/viewer/hooks.ts index ec0acc8e..ac7919b3 100644 --- a/src/common/viewer/hooks.ts +++ b/src/common/viewer/hooks.ts @@ -1,18 +1,22 @@ -import { useMemo } from "react"; +import { useContext, useMemo } from "react"; import { prop } from "remeda"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { useWalletContext } from "@/common/contexts/wallet"; import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; import { AccountId } from "@/common/types"; import { useGlobalStoreSelector } from "@/store"; +import { WalletContext } from "./internal/wallet-context"; import { ViewerSession } from "./types"; +/** + * Heads Up! + * Ensure the consuming layout is wrapped in `ViewerSessionProvider` on the topmost level. + */ export const useViewerSession = (): ViewerSession => { - const wallet = useWalletContext(); + const wallet = useContext(WalletContext); const { actAsDao } = useGlobalStoreSelector(prop("nav")); const isDaoRepresentative = actAsDao.toggle && Boolean(actAsDao.defaultAddress); diff --git a/src/common/viewer/internal/wallet-context.ts b/src/common/viewer/internal/wallet-context.ts new file mode 100644 index 00000000..a8df80fa --- /dev/null +++ b/src/common/viewer/internal/wallet-context.ts @@ -0,0 +1,15 @@ +import { createContext } from "react"; + +import type { AccountId } from "@/common/types"; + +type WalletContextState = + | { isReady: false; isSignedIn: false; accountId: undefined } + | { isReady: true; isSignedIn: boolean; accountId?: AccountId }; + +export const initialWalletContextState: WalletContextState = { + isReady: false, + isSignedIn: false, + accountId: undefined, +}; + +export const WalletContext = createContext(initialWalletContextState); diff --git a/src/common/contexts/wallet.tsx b/src/common/viewer/internal/wallet-provider.tsx similarity index 76% rename from src/common/contexts/wallet.tsx rename to src/common/viewer/internal/wallet-provider.tsx index b75033d9..5dda81ed 100644 --- a/src/common/contexts/wallet.tsx +++ b/src/common/viewer/internal/wallet-provider.tsx @@ -1,20 +1,9 @@ -import { createContext, useCallback, useContext, useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { nearClient } from "@/common/api/near"; +import type { AccountId } from "@/common/types"; -import type { AccountId } from "../types"; - -type WalletContextState = - | { isReady: false; isSignedIn: false; accountId: undefined } - | { isReady: true; isSignedIn: boolean; accountId?: AccountId }; - -const initialWalletContextState: WalletContextState = { - isReady: false, - isSignedIn: false, - accountId: undefined, -}; - -const WalletContext = createContext(initialWalletContextState); +import { WalletContext, initialWalletContextState } from "./wallet-context"; export type WalletProviderProps = { children: React.ReactNode; @@ -72,8 +61,3 @@ export const WalletProvider: React.FC = ({ children }) => { ); }; - -/** - * To be used ONLY for session management! - */ -export const useWalletContext = () => useContext(WalletContext); diff --git a/src/layout/campaign/components/layout.tsx b/src/layout/campaign/components/layout.tsx index 481b902d..dd82b3d7 100644 --- a/src/layout/campaign/components/layout.tsx +++ b/src/layout/campaign/components/layout.tsx @@ -1,10 +1,8 @@ import { useCallback, useState } from "react"; -import { isClient } from "@wpdas/naxios"; import Link from "next/link"; import { useRouter } from "next/router"; -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { cn } from "@/common/ui/utils"; @@ -107,7 +105,7 @@ export const CampaignLayout: React.FC = ({ children }) => { ); return ( - }> + }>
diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index 472259f8..3fe1b3d0 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -7,10 +7,10 @@ import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; import { nearClient } from "@/common/api/near"; -import { WalletProvider } from "@/common/contexts/wallet"; import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; +import { WalletProvider } from "@/common/viewer/internal/wallet-provider"; import { CartLink } from "@/entities/cart"; import { rootPathnames } from "@/pathnames"; diff --git a/src/layout/pot/components/layout.tsx b/src/layout/pot/components/layout.tsx index 9fffdd47..f894edef 100644 --- a/src/layout/pot/components/layout.tsx +++ b/src/layout/pot/components/layout.tsx @@ -1,13 +1,12 @@ import { useCallback, useState } from "react"; -import { isClient } from "@wpdas/naxios"; import Link from "next/link"; import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; +import { ViewerSessionProvider } from "@/common/viewer"; import { ChallengeModal } from "@/entities/pot"; import { DonationSybilWarning } from "@/features/donation"; import { MatchingPoolContributionModal } from "@/features/matching-pool-contribution"; @@ -38,10 +37,8 @@ export const PotLayout: React.FC = ({ children }) => { const [challengeModalOpen, setChallengeModalOpen] = useState(false); const openChallengeModal = useCallback(() => setChallengeModalOpen(true), []); - return !isClient() ? ( - - ) : ( - + return ( + }> {/** * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE @@ -129,6 +126,6 @@ export const PotLayout: React.FC = ({ children }) => { {/* Tab Content */}
{children}
-
+ ); }; diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index 45fe9640..a129b51a 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -1,13 +1,12 @@ import { useEffect, useState } from "react"; -import { isClient } from "@wpdas/naxios"; import Link from "next/link"; import { useRouter } from "next/router"; import { useRegistration } from "@/common/_deprecated/useRegistration"; -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; +import { ViewerSessionProvider } from "@/common/viewer"; import { ProjectBanner } from "@/entities/project"; import { ProfileLayoutControls } from "./ProfileLayoutControls"; @@ -152,10 +151,8 @@ export const ProfileLayout: React.FC = ({ children }) => { const isProject = isRegisteredProject; - return !isClient() ? ( - - ) : ( - + return ( + }> {isProject && } @@ -175,6 +172,6 @@ export const ProfileLayout: React.FC = ({ children }) => { {children}
- +
); }; diff --git a/src/pages/campaign/create.tsx b/src/pages/campaign/create.tsx index ec91e873..2ee13961 100644 --- a/src/pages/campaign/create.tsx +++ b/src/pages/campaign/create.tsx @@ -1,7 +1,5 @@ -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { ViewerSessionProvider } from "@/common/viewer"; import { CampaignForm, useCampaignDeploymentRedirect } from "@/entities/campaign"; export default function CreateCampaign() { @@ -21,13 +19,9 @@ export default function CreateCampaign() {
- {!isClient() ? ( - - ) : ( - - - - )} + }> + + ); } diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index 873a4c5e..d47f8bc3 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -1,10 +1,8 @@ import { useEffect, useState } from "react"; -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { Campaign, campaignsContractClient } from "@/common/contracts/core"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { ViewerSessionProvider } from "@/common/viewer"; import { CampaignBanner } from "@/entities/campaign/components/CampaignBanner"; import { CampaignsList } from "@/entities/campaign/components/CampaignsList"; import { FeaturedCampaigns } from "@/entities/campaign/components/FeaturedCampaigns"; @@ -33,5 +31,9 @@ export default function CampaignsPage() { } CampaignsPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? {page} : ; + return ( + }> + {page} + + ); }; diff --git a/src/pages/deploy.tsx b/src/pages/deploy.tsx index 60603a78..1397e899 100644 --- a/src/pages/deploy.tsx +++ b/src/pages/deploy.tsx @@ -1,9 +1,7 @@ -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; import InfoIcon from "@/common/ui/svg/InfoIcon"; import { cn } from "@/common/ui/utils"; +import { ViewerSessionProvider } from "@/common/viewer"; import { PotConfigurationEditor, usePotDeploymentSuccessMiddleware, @@ -12,10 +10,8 @@ import { export default function PotDeployPage() { usePotDeploymentSuccessMiddleware(); - return !isClient() ? ( - - ) : ( - + return ( + }>
- + ); } diff --git a/src/pages/edit-project/[projectId].tsx b/src/pages/edit-project/[projectId].tsx index 610d7968..60ff81dc 100644 --- a/src/pages/edit-project/[projectId].tsx +++ b/src/pages/edit-project/[projectId].tsx @@ -1,9 +1,6 @@ -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SpinnerOverlay, SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { ViewerSessionProvider, useViewerSession } from "@/common/viewer"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; @@ -45,5 +42,9 @@ export default function EditProjectPage() { } EditProjectPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? {page} : ; + return ( + }> + {page} + + ); }; diff --git a/src/pages/feed/[account]/[block]/index.tsx b/src/pages/feed/[account]/[block]/index.tsx index fab3286d..a38219c5 100644 --- a/src/pages/feed/[account]/[block]/index.tsx +++ b/src/pages/feed/[account]/[block]/index.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; -import { isClient } from "@wpdas/naxios"; import { useRouter } from "next/router"; import { LazyLoadImage } from "react-lazy-load-image-component"; import ReactMarkdown from "react-markdown"; @@ -8,8 +7,8 @@ import remarkGfm from "remark-gfm"; import { fetchSinglePost, fetchTimeByBlockHeight } from "@/common/api/near-social"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; -import { WalletProvider } from "@/common/contexts/wallet"; import { SplashScreen } from "@/common/ui/components"; +import { ViewerSessionProvider } from "@/common/viewer"; import { AccountProfilePicture } from "@/entities/_shared/account"; export default function FeedAccountBlockPostPage() { @@ -113,5 +112,9 @@ export default function FeedAccountBlockPostPage() { } FeedAccountBlockPostPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? {page} : ; + return ( + }> + {page} + + ); }; diff --git a/src/pages/feed/index.tsx b/src/pages/feed/index.tsx index 3f71c89a..2c26df08 100644 --- a/src/pages/feed/index.tsx +++ b/src/pages/feed/index.tsx @@ -1,14 +1,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { isClient } from "@wpdas/naxios"; import InfiniteScrollWrapper from "react-infinite-scroll-component"; import { indexer } from "@/common/api/indexer"; import { fetchGlobalFeeds } from "@/common/api/near-social"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { WalletProvider } from "@/common/contexts/wallet"; import { SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; +import { ViewerSessionProvider } from "@/common/viewer"; import { PostCard } from "@/entities/post"; export default function FeedPage() { @@ -113,5 +112,9 @@ export default function FeedPage() { } FeedPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? {page} : ; + return ( + }> + {page} + + ); }; diff --git a/src/pages/list/create/index.tsx b/src/pages/list/create/index.tsx index c5310e40..67e480b1 100644 --- a/src/pages/list/create/index.tsx +++ b/src/pages/list/create/index.tsx @@ -1,9 +1,5 @@ -import React from "react"; - -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { ViewerSessionProvider } from "@/common/viewer"; import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function Page() { @@ -11,13 +7,9 @@ export default function Page() { - {!isClient() ? ( - - ) : ( - - - - )} + }> + + ); } diff --git a/src/pages/list/duplicate/[id].tsx b/src/pages/list/duplicate/[id].tsx index 67d52f52..c56e0dfa 100644 --- a/src/pages/list/duplicate/[id].tsx +++ b/src/pages/list/duplicate/[id].tsx @@ -1,7 +1,5 @@ -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { ViewerSessionProvider } from "@/common/viewer"; import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function DuplicateList() { @@ -9,13 +7,9 @@ export default function DuplicateList() { - {!isClient() ? ( - - ) : ( - - - - )} + }> + + ); } diff --git a/src/pages/list/edit/[id]/index.tsx b/src/pages/list/edit/[id]/index.tsx index 4dc7382b..8b4a19e0 100644 --- a/src/pages/list/edit/[id]/index.tsx +++ b/src/pages/list/edit/[id]/index.tsx @@ -1,9 +1,7 @@ import React from "react"; -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { ViewerSessionProvider } from "@/common/viewer"; import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function Page() { @@ -11,13 +9,9 @@ export default function Page() { - {!isClient() ? ( - - ) : ( - - - - )} + }> + + ); } diff --git a/src/pages/register.tsx b/src/pages/register.tsx index a99b0391..756686da 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,9 +1,6 @@ -import { isClient } from "@wpdas/naxios"; - -import { WalletProvider } from "@/common/contexts/wallet"; import { PageWithBanner, SpinnerOverlay, SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { ViewerSessionProvider, useViewerSession } from "@/common/viewer"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; @@ -45,5 +42,9 @@ export default function RegisterPage() { } RegisterPage.getLayout = function getLayout(page: React.ReactNode) { - return isClient() ? {page} : ; + return ( + }> + {page} + + ); }; From b11b488f14b43faacc887a04209d6b8c7ac61533 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Mon, 20 Jan 2025 03:55:51 +0100 Subject: [PATCH 16/84] updated for addFundingSources schema object --- src/features/profile-setup/models/schemas.ts | 58 +++++++++----------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/src/features/profile-setup/models/schemas.ts b/src/features/profile-setup/models/schemas.ts index cf2171fa..ce3cbf06 100644 --- a/src/features/profile-setup/models/schemas.ts +++ b/src/features/profile-setup/models/schemas.ts @@ -1,5 +1,29 @@ import { array, boolean, object, string } from "zod"; +export const addFundingSourceSchema = object({ + investorName: string({ + required_error: "Please enter the investor name.", + }) + .min(3, "Must be at least 3 characters") + .max(50, "Must be less than 50 characters long"), + + date: string().optional(), + + description: string({ + required_error: "Please enter description.", + }).max(500, "Must be less than 500 characters long"), + + amountReceived: string({ + required_error: "Please enter the investment amount.", + }), + + denomination: string({ + required_error: "Please enter the denomination.", + }) + .min(3, "Must be at least 3 characters") + .max(50, "Must be less than 50 characters"), +}); + export const projectEditorSchema = object({ name: string() .min(3, "Must be at least 3 characters long") @@ -23,15 +47,7 @@ export const projectEditorSchema = object({ smartContracts: array(array(string())).optional(), - fundingSources: array( - object({ - investorName: string(), - date: string().optional(), - description: string(), - amountReceived: string(), - denomination: string(), - }), - ).optional(), + fundingSources: array(addFundingSourceSchema).optional(), githubRepositories: array(string()).optional(), website: string().optional(), @@ -39,27 +55,3 @@ export const projectEditorSchema = object({ telegram: string().optional(), github: string().optional(), }); - -export const addFundingSourceSchema = object({ - investorName: string({ - required_error: "Please enter the investor name.", - }) - .min(3, "Must be at least 3 characters") - .max(50, "Must be less than 50 characters long"), - - date: string().optional(), - - description: string({ - required_error: "Please enter description.", - }).max(500, "Must be less than 500 characters long"), - - amountReceived: string({ - required_error: "Please enter the investment amount.", - }), - - denomination: string({ - required_error: "Please enter the denomination.", - }) - .min(3, "Must be at least 3 characters") - .max(50, "Must be less than 50 characters"), -}); From 3c3530544ba56f632db8dc6048164b63699fc788 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Mon, 20 Jan 2025 04:38:40 +0000 Subject: [PATCH 17/84] wip --- src/common/api/near/client.ts | 4 +- src/common/viewer/components.tsx | 4 +- src/common/viewer/hooks.ts | 59 +++++++++++-------- src/common/viewer/internal/wallet-context.ts | 17 +++++- .../viewer/internal/wallet-provider.tsx | 34 ++++------- src/common/viewer/types.ts | 23 ++++++-- .../campaign/components/CampaignSettings.tsx | 9 ++- src/entities/campaign/hooks/forms.ts | 17 +++--- src/entities/list/components/ListCard.tsx | 5 +- src/entities/list/components/ListDetails.tsx | 19 +++--- .../voting-round/components/CandidateRow.tsx | 6 +- .../donation/components/DonationModal.tsx | 6 +- .../components/DonationSybilWarning.tsx | 5 +- src/features/donation/hooks/forms.ts | 14 ++--- .../components/PotConfigurationPreview.tsx | 8 ++- src/features/pot-configuration/hooks/forms.ts | 7 ++- src/layout/components/app-bar.tsx | 40 +++++++------ 17 files changed, 160 insertions(+), 117 deletions(-) diff --git a/src/common/api/near/client.ts b/src/common/api/near/client.ts index 8eb3cdde..47618e72 100644 --- a/src/common/api/near/client.ts +++ b/src/common/api/near/client.ts @@ -70,12 +70,12 @@ export const naxiosInstance = new naxios({ }); /** - * NEAR Wallet API + * DO NOT USE DIRECTLY! */ export const walletApi = naxiosInstance.walletApi(); /** - * NEAR RPC API Provider3 + * NEAR JsonRpcProvider */ export const nearRpc = naxiosInstance.rpcApi(); diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx index a7f0a554..2c75ea77 100644 --- a/src/common/viewer/components.tsx +++ b/src/common/viewer/components.tsx @@ -6,7 +6,7 @@ import { WalletProvider } from "./internal/wallet-provider"; export type ViewerSessionProviderProps = { children: React.ReactNode; - ssrFallback: React.ReactNode; + ssrFallback?: React.ReactNode; }; /** @@ -19,5 +19,5 @@ export const ViewerSessionProvider: React.FC = ({ }) => { const isCsr = useMemo(isClient, []); - return isCsr ? {children} : ssrFallback; + return isCsr ? {children} : children; }; diff --git a/src/common/viewer/hooks.ts b/src/common/viewer/hooks.ts index ac7919b3..1529cb19 100644 --- a/src/common/viewer/hooks.ts +++ b/src/common/viewer/hooks.ts @@ -5,7 +5,6 @@ import { prop } from "remeda"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; -import { AccountId } from "@/common/types"; import { useGlobalStoreSelector } from "@/store"; import { WalletContext } from "./internal/wallet-context"; @@ -20,11 +19,11 @@ export const useViewerSession = (): ViewerSession => { const { actAsDao } = useGlobalStoreSelector(prop("nav")); const isDaoRepresentative = actAsDao.toggle && Boolean(actAsDao.defaultAddress); - const accountId: AccountId | undefined = useMemo(() => { - if (wallet.isReady) { + const accountId = useMemo(() => { + if (wallet.isSignedIn) { return isDaoRepresentative ? actAsDao.defaultAddress : wallet.accountId; } else return undefined; - }, [actAsDao.defaultAddress, isDaoRepresentative, wallet.accountId, wallet.isReady]); + }, [actAsDao.defaultAddress, isDaoRepresentative, wallet.accountId, wallet.isSignedIn]); /** * Account for edge cases in which the wallet is connected to a mismatching network @@ -39,23 +38,37 @@ export const useViewerSession = (): ViewerSession => { listId: PUBLIC_GOODS_REGISTRY_LIST_ID, }); - if (wallet.isSignedIn && accountId && isAccountIdValid) { - return { - accountId, - isSignedIn: true, - isMetadataLoading: isRegistrationLoading, - hasRegistrationSubmitted: registration !== undefined, - hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, - registrationStatus: registration?.status, - }; - } else { - return { - accountId: undefined, - isSignedIn: false, - isMetadataLoading: false, - hasRegistrationSubmitted: false, - hasRegistrationApproved: false, - registrationStatus: undefined, - }; - } + return useMemo(() => { + if (wallet.isReady && wallet.isSignedIn && accountId) { + return { + hasWalletReady: true, + accountId, + isSignedIn: true, + isMetadataLoading: isRegistrationLoading, + hasRegistrationSubmitted: registration !== undefined, + hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, + registrationStatus: registration?.status, + }; + } else if (wallet.isReady && !wallet.isSignedIn) { + return { + hasWalletReady: true, + accountId: undefined, + isSignedIn: false, + isMetadataLoading: false, + hasRegistrationSubmitted: false, + hasRegistrationApproved: false, + registrationStatus: undefined, + }; + } else { + return { + hasWalletReady: false, + accountId: undefined, + isSignedIn: false, + isMetadataLoading: false, + hasRegistrationSubmitted: false, + hasRegistrationApproved: false, + registrationStatus: undefined, + }; + } + }, [accountId, isRegistrationLoading, registration, wallet.isReady, wallet.isSignedIn]); }; diff --git a/src/common/viewer/internal/wallet-context.ts b/src/common/viewer/internal/wallet-context.ts index a8df80fa..78283acf 100644 --- a/src/common/viewer/internal/wallet-context.ts +++ b/src/common/viewer/internal/wallet-context.ts @@ -1,15 +1,30 @@ import { createContext } from "react"; +import { create } from "zustand"; + import type { AccountId } from "@/common/types"; type WalletContextState = | { isReady: false; isSignedIn: false; accountId: undefined } | { isReady: true; isSignedIn: boolean; accountId?: AccountId }; -export const initialWalletContextState: WalletContextState = { +const initialWalletContextState: WalletContextState = { isReady: false, isSignedIn: false, accountId: undefined, }; export const WalletContext = createContext(initialWalletContextState); + +type WalletContextStore = WalletContextState & { + initialize: (isReady: boolean) => void; + setIsSignedIn: (isSignedIn: boolean) => void; + setAccountId: (accountId: AccountId | undefined) => void; +}; + +export const useWalletContextStore = create((set) => ({ + ...initialWalletContextState, + initialize: (isReady: boolean) => set(isReady ? { isReady } : initialWalletContextState), + setIsSignedIn: (isSignedIn: boolean) => set({ isSignedIn }), + setAccountId: (accountId: AccountId | undefined) => set({ accountId }), +})); diff --git a/src/common/viewer/internal/wallet-provider.tsx b/src/common/viewer/internal/wallet-provider.tsx index 5dda81ed..b232f771 100644 --- a/src/common/viewer/internal/wallet-provider.tsx +++ b/src/common/viewer/internal/wallet-provider.tsx @@ -1,40 +1,38 @@ import { useCallback, useEffect, useState } from "react"; import { nearClient } from "@/common/api/near"; -import type { AccountId } from "@/common/types"; -import { WalletContext, initialWalletContextState } from "./wallet-context"; +import { WalletContext, useWalletContextStore } from "./wallet-context"; export type WalletProviderProps = { children: React.ReactNode; }; export const WalletProvider: React.FC = ({ children }) => { - const [isReady, setIsReady] = useState(false); const [error, setError] = useState(null); - const [isSignedIn, setIsSignedIn] = useState(false); - const [accountId, setAccountId] = useState(undefined); + const { initialize, setIsSignedIn, setAccountId, ...contextState } = useWalletContextStore(); const syncWalletState = useCallback(() => { setIsSignedIn(nearClient.walletApi.walletSelector.isSignedIn()); setAccountId(nearClient.walletApi.accountId); - }, []); + }, [setAccountId, setIsSignedIn]); useEffect(() => { - if (!isReady && error !== null) { + if (!contextState.isReady && error === null) { nearClient.walletApi .initNear() - .then(() => setIsReady(true)) + .then(() => initialize(true)) .catch((error) => { console.log(error); setError(error); - setIsReady(false); }); } - }, [error, isReady]); + }, [error, initialize, contextState.isReady]); useEffect(() => { - if (isReady) { + if (contextState.isReady) { + syncWalletState(); + nearClient.walletApi.walletSelector.on("signedIn", syncWalletState); nearClient.walletApi.walletSelector.on("signedOut", syncWalletState); nearClient.walletApi.walletSelector.on("accountsChanged", syncWalletState); @@ -43,7 +41,7 @@ export const WalletProvider: React.FC = ({ children }) => { } return () => { - if (isReady) { + if (contextState.isReady) { nearClient.walletApi.walletSelector.off("signedIn", syncWalletState); nearClient.walletApi.walletSelector.off("signedOut", syncWalletState); nearClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); @@ -51,13 +49,7 @@ export const WalletProvider: React.FC = ({ children }) => { nearClient.walletApi.walletSelector.off("uriChanged", syncWalletState); } }; - }, [syncWalletState, isReady]); - - return ( - - {children} - - ); + }, [syncWalletState, contextState.isReady]); + + return {children}; }; diff --git a/src/common/viewer/types.ts b/src/common/viewer/types.ts index def5706a..3ffa6134 100644 --- a/src/common/viewer/types.ts +++ b/src/common/viewer/types.ts @@ -3,18 +3,29 @@ import { AccountId } from "@/common/types"; export type ViewerSession = | { - accountId: AccountId; - registrationStatus?: RegistrationStatus; - isSignedIn: true; - isMetadataLoading: boolean; - hasRegistrationSubmitted: boolean; - hasRegistrationApproved: boolean; + hasWalletReady: false; + accountId: undefined; + registrationStatus: undefined; + isSignedIn: false; + isMetadataLoading: false; + hasRegistrationSubmitted: false; + hasRegistrationApproved: false; } | { + hasWalletReady: true; accountId: undefined; registrationStatus: undefined; isSignedIn: false; isMetadataLoading: false; hasRegistrationSubmitted: false; hasRegistrationApproved: false; + } + | { + hasWalletReady: true; + accountId: AccountId; + registrationStatus?: RegistrationStatus; + isSignedIn: true; + isMetadataLoading: boolean; + hasRegistrationSubmitted: boolean; + hasRegistrationApproved: boolean; }; diff --git a/src/entities/campaign/components/CampaignSettings.tsx b/src/entities/campaign/components/CampaignSettings.tsx index bd817bb7..a250b049 100644 --- a/src/entities/campaign/components/CampaignSettings.tsx +++ b/src/entities/campaign/components/CampaignSettings.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import { walletApi } from "@/common/api/near/client"; import { useRouteQuery, yoctoNearToFloat } from "@/common/lib"; import { NearIcon } from "@/common/ui/svg"; +import { useViewerSession } from "@/common/viewer"; import { AccountProfilePicture } from "@/entities/_shared/account"; import { CampaignForm } from "./CampaignForm"; @@ -12,13 +13,15 @@ import { useCampaignDeploymentRedirect } from "../hooks/redirects"; import { useCampaign } from "../hooks/useCampaign"; export const CampaignSettings = () => { + // TODO: Move this call to the corresponding page! useCampaignDeploymentRedirect(); - const [openEditCampaign, setOpenEditCampaign] = useState(false); const { query: { campaignId }, } = useRouteQuery(); + const viewer = useViewerSession(); + const [openEditCampaign, setOpenEditCampaign] = useState(false); const { campaign } = useCampaign({ campaignId: campaignId as string }); if (!campaign) return <>; @@ -61,7 +64,8 @@ export const CampaignSettings = () => {
- {walletApi.accountId === campaign?.owner && ( + + {viewer.isSignedIn && viewer.accountId === campaign?.owner && (

setOpenEditCampaign(!openEditCampaign)} @@ -73,6 +77,7 @@ export const CampaignSettings = () => {

)}
+ {!openEditCampaign ? (
diff --git a/src/entities/campaign/hooks/forms.ts b/src/entities/campaign/hooks/forms.ts index 46f7ae9a..6ade6337 100644 --- a/src/entities/campaign/hooks/forms.ts +++ b/src/entities/campaign/hooks/forms.ts @@ -5,16 +5,19 @@ import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; import { Temporal } from "temporal-polyfill"; import { infer as FromSchema, ZodError } from "zod"; -import { walletApi } from "@/common/api/near/client"; import { campaignsContractClient } from "@/common/contracts/core"; import { floatToYoctoNear, useRouteQuery } from "@/common/lib"; +import { useViewerSession } from "@/common/viewer"; import { dispatch } from "@/store"; import { campaignFormSchema } from "../models/schema"; import { CampaignEnumType } from "../types"; export const useCampaignForm = () => { + const viewer = useViewerSession(); + const { + // TODO: Pass this values down from the page level! query: { campaignId }, } = useRouteQuery(); @@ -66,7 +69,7 @@ export const useCampaignForm = () => { ...(values.end_ms && { end_ms: timeToMiliSeconds(values.end_ms.toString()).epochMilliseconds, }), - ...(campaignId ? {} : { owner: walletApi.accountId as string }), + ...(campaignId ? {} : { owner: viewer.accountId as string }), ...(campaignId ? {} : { recipient: values.recipient }), }; @@ -76,23 +79,23 @@ export const useCampaignForm = () => { }); dispatch.campaignEditor.updateCampaignModalState({ - header: `You’ve successfully created a campaignsContractClient for ${values.name}.`, + header: `You’ve successfully created a campaign for ${values.name}.`, description: - "If you are not a member of the project, the campaignsContractClient will be considered unofficial until it has been approved by the project.", + "If you are not a member of the project, the campaign will be considered unofficial until it has been approved by the project.", type: CampaignEnumType.UPDATE_CAMPAIGN, }); } else { campaignsContractClient.create_campaign({ args }); dispatch.campaignEditor.updateCampaignModalState({ - header: `You’ve successfully created a campaignsContractClient for ${values.name}.`, + header: `You’ve successfully created a campaign for ${values.name}.`, description: - "If you are not a member of the project, the campaignsContractClient will be considered unofficial until it has been approved by the project.", + "If you are not a member of the project, the campaign will be considered unofficial until it has been approved by the project.", type: CampaignEnumType.CREATE_CAMPAIGN, }); } }, - [campaignId], + [campaignId, viewer.accountId], ); const onChange = async (field: keyof Values, value: string) => { diff --git a/src/entities/list/components/ListCard.tsx b/src/entities/list/components/ListCard.tsx index c2ccdb7e..bb068f09 100644 --- a/src/entities/list/components/ListCard.tsx +++ b/src/entities/list/components/ListCard.tsx @@ -5,11 +5,11 @@ import { useRouter } from "next/router"; import { FaHeart } from "react-icons/fa"; import { LazyLoadImage } from "react-lazy-load-image-component"; -import { walletApi } from "@/common/api/near/client"; import { listsContractClient } from "@/common/contracts/core"; import { truncate } from "@/common/lib"; import { LayersIcon } from "@/common/ui/svg"; import { LikeIcon } from "@/common/ui/svg/like"; +import { useViewerSession } from "@/common/viewer"; import { AccountProfilePicture } from "@/entities/_shared/account"; import { dispatch } from "@/store"; @@ -24,11 +24,12 @@ export const ListCard = ({ background?: string; backdrop: string; }) => { + const viewer = useViewerSession(); const [isUpvoted, setIsUpvoted] = useState(false); const { push } = useRouter(); useEffect(() => { - setIsUpvoted(dataForList.upvotes?.some((data: any) => data?.account === walletApi.accountId)); + setIsUpvoted(dataForList.upvotes?.some((data: any) => data?.account === viewer.accountId)); }, [dataForList]); const handleRoute = useCallback( diff --git a/src/entities/list/components/ListDetails.tsx b/src/entities/list/components/ListDetails.tsx index ae935973..6e69e141 100644 --- a/src/entities/list/components/ListDetails.tsx +++ b/src/entities/list/components/ListDetails.tsx @@ -9,7 +9,6 @@ import { LazyLoadImage } from "react-lazy-load-image-component"; import { prop } from "remeda"; import { List } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; import { listsContractClient } from "@/common/contracts/core"; import { truncate } from "@/common/lib"; import { @@ -21,6 +20,7 @@ import { } from "@/common/ui/components"; import { SocialsShare } from "@/common/ui/components/molecules/social-share"; import { AdminUserIcon, DeleteListIcon, DotsIcons, PenIcon } from "@/common/ui/svg"; +import { useViewerSession } from "@/common/viewer"; import { AccountGroupEditModal, AccountListItem, @@ -43,8 +43,11 @@ interface ListDetailsType { } export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType) => { + const viewer = useViewerSession(); + const { push, + // TODO: Pass this values down from the page level! query: { id }, } = useRouter(); @@ -55,9 +58,7 @@ export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType const adminsModalId = useId(); const registrantsModalId = useId(); - const isUpvoted = listDetails?.upvotes?.some( - (data: any) => data?.account === walletApi.accountId, - ); + const isUpvoted = listDetails?.upvotes?.some((data: any) => data?.account === viewer.accountId); const openExistingAccountModal = useCallback( () => show(registrantsModalId), @@ -83,9 +84,9 @@ export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType notes: note, registrations: [ { - registrant_id: walletApi?.accountId ?? "", + registrant_id: viewer.accountId ?? "", status: - listDetails?.owner?.id === walletApi.accountId + listDetails?.owner?.id === viewer.accountId ? "Approved" : listDetails?.default_registration_status, @@ -122,7 +123,7 @@ export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType } const isAdminOrGreater = - admins.includes(walletApi?.accountId ?? "") || listDetails.owner?.id === walletApi?.accountId; + admins.includes(viewer.accountId ?? "") || listDetails.owner?.id === viewer.accountId; const handleUpvote = () => { if (isUpvoted) { @@ -205,7 +206,7 @@ export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType )}
- {listDetails.owner?.id === walletApi?.accountId && ( + {listDetails.owner?.id === viewer.accountId && (
- {Boolean(walletApi?.accountId) && ( + {viewer.isSignedIn && (
diff --git a/src/entities/voting-round/components/CandidateRow.tsx b/src/entities/voting-round/components/CandidateRow.tsx index 106a677e..1ff8aaa1 100644 --- a/src/entities/voting-round/components/CandidateRow.tsx +++ b/src/entities/voting-round/components/CandidateRow.tsx @@ -25,17 +25,17 @@ export const VotingRoundCandidateRow: React.FC = ( isSelected = false, onSelect, }) => { - const user = useViewerSession(); + const viewer = useViewerSession(); const { isLoading, canReceiveVotes, hasUserVotes, handleVoteCast } = useVotingRoundCandidateEntry( { electionId, accountId }, ); const unableToVoteError = useMemo(() => { - if (user.isSignedIn) { + if (viewer.isSignedIn) { if (!canReceiveVotes) return "You cannot vote for this project."; } else return "Please sign in to vote."; - }, [canReceiveVotes, user.isSignedIn]); + }, [canReceiveVotes, viewer.isSignedIn]); const onCheckTriggered = useCallback( (checked: CheckedState) => onSelect?.(accountId, Boolean(checked)), diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index 6d76fb7e..e158ac95 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { create, useModal } from "@ebay/nice-modal-react"; -import { walletApi } from "@/common/api/near/client"; +import { nearClient } from "@/common/api/near"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; @@ -17,13 +17,13 @@ export type DonationModalProps = DonationAllocationKey & Pick & {}; export const DonationModal = create((props: DonationModalProps) => { + const viewer = useViewerSession(); const self = useModal(); const isSingleProjectDonation = "accountId" in props; const isPotDonation = "potId" in props; const isListDonation = "listId" in props; const isCampaignDonation = "campaignId" in props; const { currentStep } = useDonationState(); - const viewer = useViewerSession(); const { setSearchParams } = useRouteQuery(); const close = useCallback(() => { @@ -41,7 +41,7 @@ export const DonationModal = create((props: DonationModalProps) => { }, [self, setSearchParams]); const onSignInClick = useCallback(() => { - walletApi.signInModal(); + nearClient.walletApi.signInModal(); close(); }, [close]); diff --git a/src/features/donation/components/DonationSybilWarning.tsx b/src/features/donation/components/DonationSybilWarning.tsx index 80ba43c4..0132f55e 100644 --- a/src/features/donation/components/DonationSybilWarning.tsx +++ b/src/features/donation/components/DonationSybilWarning.tsx @@ -3,9 +3,9 @@ import { useMemo } from "react"; import { SYBIL_APP_LINK_URL } from "@/common/_config"; import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { ByPotId, indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; import { Alert, AlertDescription, AlertTitle, Button } from "@/common/ui/components"; import { WarningIcon } from "@/common/ui/svg"; +import { useViewerSession } from "@/common/viewer"; export type DonationSybilWarningProps = ByPotId & { classNames?: { @@ -17,7 +17,8 @@ export const DonationSybilWarning: React.FC = ({ potId, classNames, }) => { - const { isHumanVerified: isDonorNadabotVerified } = useIsHuman(walletApi.accountId); + const viewer = useViewerSession(); + const { isHumanVerified: isDonorNadabotVerified } = useIsHuman(viewer.accountId); const { data: pot } = indexer.usePot({ potId }); const isVisible = useMemo( diff --git a/src/features/donation/hooks/forms.ts b/src/features/donation/hooks/forms.ts index db9974d9..fc4139ec 100644 --- a/src/features/donation/hooks/forms.ts +++ b/src/features/donation/hooks/forms.ts @@ -8,9 +8,9 @@ import { ZodError } from "zod"; import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PotApplicationStatus, indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; import { NATIVE_TOKEN_ID } from "@/common/constants"; import { toChronologicalOrder } from "@/common/lib"; +import { useViewerSession } from "@/common/viewer"; import { dispatch } from "@/store"; import { DONATION_MIN_NEAR_AMOUNT, DONATION_MIN_NEAR_AMOUNT_ERROR } from "../constants"; @@ -27,6 +27,7 @@ export type DonationFormParams = DonationAllocationKey & { }; export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormParams) => { + const viewer = useViewerSession(); const isSingleProjectDonation = "accountId" in params; const isPotDonation = "potId" in params; const isListDonation = "listId" in params; @@ -34,7 +35,6 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa const potAccountId = isPotDonation ? params.potId : undefined; const listId = isListDonation ? params.listId : undefined; const campaignId = isCampaignDonation ? params.campaignId : undefined; - const recipientAccountId = isSingleProjectDonation ? params.accountId : undefined; const { data: recipientActivePots = [] } = indexer.useAccountActivePots({ @@ -52,7 +52,6 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa const defaultPotAccountId = useMemo( () => toChronologicalOrder("matching_round_end", matchingPots).at(0)?.account, - [matchingPots], ); @@ -142,7 +141,7 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa const isDisabled = !hasChanges || !self.formState.isValid || self.formState.isSubmitting; - const isSenderHumanVerified = useIsHuman(walletApi.accountId ?? "noop"); + const isSenderHumanVerified = useIsHuman(viewer.accountId); const minAmountError = !isDonationAmountSufficient({ amount, tokenId }) && hasChanges @@ -156,7 +155,7 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa useEffect(() => { /** - *? Due to an unknown issue, when `defaultPotAccountId` gets determined, + *? Due to yet undetermined issue, when `defaultPotAccountId` gets defined, *? it does not trigger the form state update, so we have to do it manually. */ if ( @@ -173,10 +172,7 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa (values.allocationStrategy === "full" && values.tokenId === undefined) || (values.allocationStrategy === "share" && values.tokenId !== NATIVE_TOKEN_ID) ) { - self.setValue("tokenId", NATIVE_TOKEN_ID, { - shouldDirty: true, - shouldTouch: true, - }); + self.setValue("tokenId", NATIVE_TOKEN_ID, { shouldDirty: true, shouldTouch: true }); } }, [self, values]); diff --git a/src/features/pot-configuration/components/PotConfigurationPreview.tsx b/src/features/pot-configuration/components/PotConfigurationPreview.tsx index a8bb43bb..f8908262 100644 --- a/src/features/pot-configuration/components/PotConfigurationPreview.tsx +++ b/src/features/pot-configuration/components/PotConfigurationPreview.tsx @@ -4,10 +4,10 @@ import { Pencil } from "lucide-react"; import { entries, isStrictEqual, omit, piped, prop } from "remeda"; import { ByPotId, type PotId, indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; import { isAccountId } from "@/common/lib"; import { Button, DataLoadingPlaceholder, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; +import { useViewerSession } from "@/common/viewer"; import { AccountGroup, AccountListItem, AccountProfileLink } from "@/entities/_shared/account"; import { POT_EDITOR_FIELDS } from "../constants"; @@ -59,6 +59,8 @@ export const PotConfigurationPreview: React.FC = ( onEditClick, className, }) => { + const viewer = useViewerSession(); + const { isLoading, data: pot } = indexer.usePot({ enabled: potId !== undefined, potId: potId as PotId, @@ -68,9 +70,9 @@ export const PotConfigurationPreview: React.FC = ( const isDataAvailable = pot !== undefined && adminAccountIds !== undefined; const isEditingAllowed = - walletApi.accountId !== undefined && + viewer.isSignedIn && isDataAvailable && - [...pot.admins, pot.owner].some(piped(prop("id"), isStrictEqual(walletApi.accountId))); + [...pot.admins, pot.owner].some(piped(prop("id"), isStrictEqual(viewer.accountId))); const tableContent = useMemo( () => diff --git a/src/features/pot-configuration/hooks/forms.ts b/src/features/pot-configuration/hooks/forms.ts index 54a72c8c..4a293f7d 100644 --- a/src/features/pot-configuration/hooks/forms.ts +++ b/src/features/pot-configuration/hooks/forms.ts @@ -7,9 +7,9 @@ import { infer as FromSchema, ZodError } from "zod"; import { CONTRACT_SOURCECODE_REPO_URL, CONTRACT_SOURCECODE_VERSION } from "@/common/_config"; import { ByPotId, type PotId, indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; import { PotConfig } from "@/common/contracts/core"; import { AccountId } from "@/common/types"; +import { useViewerSession } from "@/common/viewer"; import { PotInputs } from "@/entities/pot"; import { donationFeeBasisPointsToPercents } from "@/features/donation"; import { rootPathnames } from "@/pathnames"; @@ -33,6 +33,7 @@ export const usePotConfigurationEditorForm = ({ schema, ...props }: PotConfigurationEditorFormArgs) => { + const viewer = useViewerSession(); const router = useRouter(); const potId = "potId" in props ? props.potId : undefined; const isNewPot = "potId" in props && typeof potId !== "string"; @@ -62,7 +63,7 @@ export const usePotConfigurationEditorForm = ({ link: CONTRACT_SOURCECODE_REPO_URL, }, - owner: walletApi.accountId, + owner: viewer.accountId, max_projects: 25, min_matching_pool_donation_amount: 0.1, referral_fee_matching_pool_basis_points: donationFeeBasisPointsToPercents(100), @@ -73,7 +74,7 @@ export const usePotConfigurationEditorForm = ({ ...existingValues, }), - [existingValues, latestSourceCodeCommitHash], + [existingValues, latestSourceCodeCommitHash, viewer.accountId], ); const self = useForm({ diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index 3fe1b3d0..8c857b3f 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -1,16 +1,14 @@ import { useCallback, useState } from "react"; -import { isClient } from "@wpdas/naxios"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; import { nearClient } from "@/common/api/near"; -import { Button } from "@/common/ui/components"; +import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; -import { WalletProvider } from "@/common/viewer/internal/wallet-provider"; +import { ViewerSessionProvider, useViewerSession } from "@/common/viewer"; import { CartLink } from "@/entities/cart"; import { rootPathnames } from "@/pathnames"; @@ -42,17 +40,23 @@ const AuthButton = () => { nearClient.walletApi.signInModal(); }, []); - return viewer.isSignedIn ? ( - + return viewer.hasWalletReady ? ( + <> + {viewer.isSignedIn ? ( + + ) : ( + + )} + ) : ( - + ); }; @@ -168,11 +172,9 @@ export const AppBar = () => {
- {isClient() && ( - - - - )} + + + setShowMobileMenu(!showMobileMenu)} />
From 405642983b69112f1d7a595a77d0356be89ef70c Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 14:34:36 +0100 Subject: [PATCH 18/84] Updated components to completely use react-hook-form --- .../components/ProjectEditor.tsx | 219 ++++++++++-------- 1 file changed, 116 insertions(+), 103 deletions(-) diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index 111d3782..4f4711b7 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { FC, useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; @@ -7,6 +7,10 @@ import { prop } from "remeda"; import { socialDbContractHooks } from "@/common/contracts/social"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; +import { + ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + useAccountSocialProfile, +} from "@/entities/_shared/account"; import { useWallet } from "@/entities/_shared/session"; import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; import { rootPathnames } from "@/pathnames"; @@ -35,13 +39,18 @@ import SubHeader from "./SubHeader"; import SuccessfulRegister from "./SuccessfulRegister"; import { useProjectEditorForm } from "../hooks/forms"; import { useProjectEditorState } from "../models"; +import { ProjectEditorInputs } from "../models/types"; + +interface ProjectEditorProps { + accountId: string; +} -export const ProjectEditor = () => { +export const ProjectEditor: FC = ({ accountId }) => { const router = useRouter(); - const { projectId: projectIdPathParam } = router.query; - const { wallet, isWalletReady } = useWallet(); - // const [editContractIndex, setEditContractIndex] = useState(); - const [initialNameSet, setInitialNameSet] = useState(false); + const isNewAccount = accountId === undefined; + // const { wallet, isWalletReady } = useWallet(); + // // const [editContractIndex, setEditContractIndex] = useState(); + // const [initialNameSet, setInitialNameSet] = useState(false); // Local state for modals const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); @@ -49,17 +58,37 @@ export const ProjectEditor = () => { const [editFundingIndex, setEditFundingIndex] = useState(); const [editContractIndex, setEditContractIndex] = useState(); - const projectId = - typeof projectIdPathParam === "string" ? projectIdPathParam : projectIdPathParam?.at(0); - - const { data: _currentProjectData } = socialDbContractHooks.useSocialProfile({ - accountId: wallet?.accountId || "", - enabled: !!wallet?.accountId, + const { + profile: socialDbSnapshot, + avatarSrc, + backgroundSrc, + } = useAccountSocialProfile({ + accountId: accountId ?? "noop", + enabled: !isNewAccount, }); - console.log({ _currentProjectData }); - - const stateData = useProjectEditorState(); + // const stateData = useProjectEditorState(); + + const defaultValues = useMemo>( + () => + socialDbSnapshot === undefined + ? { profileImage: ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } + : { + name: socialDbSnapshot.name, + description: socialDbSnapshot.description, + publicGoodReason: socialDbSnapshot.plPublicGoodReason, + teamMembers: socialDbSnapshot.plTeam ? JSON.parse(socialDbSnapshot.plTeam) : undefined, + categories: socialDbSnapshot.plCategories + ? JSON.parse(socialDbSnapshot.plCategories) + : undefined, + github: socialDbSnapshot.plGithubRepos + ? JSON.parse(socialDbSnapshot.plGithubRepos) + : undefined, + backgroundImage: backgroundSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + profileImage: avatarSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + }, + [avatarSrc, backgroundSrc, socialDbSnapshot], + ); const { form, @@ -73,23 +102,22 @@ export const ProjectEditor = () => { resetForm, errors, } = useProjectEditorForm({ - defaultValues: _currentProjectData - ? { - ...stateData, - name: _currentProjectData.name || "", - description: _currentProjectData.description || "", - publicGoodReason: _currentProjectData.plPublicGoodReason || "", - teamMembers: JSON.parse(_currentProjectData.plTeam || "[]"), - categories: JSON.parse(_currentProjectData.plCategories || "[]"), - github: JSON.parse(_currentProjectData.plGithubRepos || "[]"), - backgroundImage: String(_currentProjectData.backgroundImage || ""), - profileImage: String(_currentProjectData.image || ""), - } - : {}, + defaultValues, onSuccess: () => router.push(rootPathnames.PROJECTS_LIST), + isEdit: !!socialDbSnapshot, }); - const isOwner = values.isDao ? projectId === values.daoAddress : projectId === wallet?.accountId; + useEffect(() => { + form.reset(defaultValues, { + keepDefaultValues: true, + keepDirty: false, + }); + }, [defaultValues]); + + const stringifiedDefaultValues = JSON.stringify(defaultValues); + const stringifiedValues = JSON.stringify(values); + + const isOwner = values.isDao ? accountId === values.daoAddress : accountId === accountId; const categoryChangeHandler = useCallback( (categories: string[]) => updateCategories(categories), @@ -106,47 +134,25 @@ export const ProjectEditor = () => { [updateRepositories], ); - const resetUrl = useCallback(() => { - router.push(rootPathnames.CREATE_PROJECT); - resetForm(); - }, [router, resetForm]); + console.log({ defaultValues }); + + const isThereDiff = useMemo(() => { + return stringifiedDefaultValues !== stringifiedValues; + }, [stringifiedValues, stringifiedDefaultValues]); + + // const resetUrl = useCallback(() => { + // router.push(rootPathnames.CREATE_PROJECT); + // resetForm(); + // }, [router, resetForm]); - // const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); + const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); const { isAuthenticated } = useSessionReduxStore(); // Add loading state const [isDataLoading, setIsDataLoading] = useState(true); - // Watch for data changes and update form - useEffect(() => { - if (_currentProjectData) { - const formattedData = { - ...stateData, - name: _currentProjectData.name || "", - description: _currentProjectData.description || "", - publicGoodReason: _currentProjectData.plPublicGoodReason || "", - teamMembers: JSON.parse(_currentProjectData.plTeam || "[]"), - categories: JSON.parse(_currentProjectData.plCategories || "[]"), - github: JSON.parse(_currentProjectData.plGithubRepos || "[]"), - backgroundImage: String(_currentProjectData.backgroundImage || ""), - profileImage: String(_currentProjectData.image || ""), - }; - - form.reset(formattedData, { - keepDefaultValues: true, - keepDirty: false, - keepErrors: false, - keepIsSubmitted: false, - keepTouched: false, - keepIsValid: false, - }); - - setIsDataLoading(false); - } - }, [_currentProjectData, form, stateData]); - // Prevent form render while loading - if (isDataLoading) { + if (form.formState.isLoading) { return ; } @@ -191,19 +197,17 @@ export const ProjectEditor = () => { // const [editFundingIndex, setEditFundingIndex] = useState(); // controls if a funding is being edited // const [editContractIndex, setEditContractIndex] = useState(); - // const getProjectEditorText = () => { - // if (values.isEdit) { - // return values.isDao ? "Add proposal to update project" : "Update your project"; - // } + const getProjectEditorText = () => { + if (socialDbSnapshot) { + return values.isDao ? "Add proposal to update project" : "Update your project"; + } - // return values.isDao ? "Add proposal to create project" : "Create new project"; - // }; + return values.isDao ? "Add proposal to create project" : "Create new project"; + }; - // const projectEditorText = getProjectEditorText(); + const projectEditorText = getProjectEditorText(); - // const isRepositoriesValid = - // !values.isRepositoryRequired || - // (values.githubRepositories && values.githubRepositories.length > 0); + const isRepositoriesValid = values.githubRepositories && values.githubRepositories.length > 0; // // Wait for wallet // if (!isWalletReady || !wallet) { @@ -214,20 +218,22 @@ export const ProjectEditor = () => { // return ; // } - // // must be signed in - // if (!isAuthenticated) { - // return ; - // } + // must be signed in + if (!accountId) { + return ; + } - // // If it is Edit & not the owner - // if (!isOwner && values.isEdit) { - // return ( - // - // ); - // } + // If it is Edit & not the owner + if (!isOwner && socialDbSnapshot) { + return ( + + ); + } + + const isEdit = !!socialDbSnapshot; // // DAO Status - In Progress // if ( @@ -238,21 +244,29 @@ export const ProjectEditor = () => { // return ; // } - // if (values.submissionStatus === "done" && location.pathname === rootPathnames.CREATE_PROJECT) { - // return ( - //
- // - //
- // ); - // } + if (form.formState.isSubmitted && location.pathname === rootPathnames.CREATE_PROJECT) { + return ( +
+ +
+ ); + } console.log("Form values:", values); console.log(form.formState.errors, form.formState.isValid); + // console.log({ stringifiedDefaultValues, stringifiedValues }); + + console.log("isThereAChange?", isThereDiff, { isSubmitting }); + + if (form.formState.isLoading) { + return ; + } + return ( // Container
@@ -260,7 +274,6 @@ export const ProjectEditor = () => { - {/* Team Member Section */}
From dcfdb28aef07f10b1354105c8523ca77737b1c1f Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 14:40:06 +0100 Subject: [PATCH 19/84] Removed unused imports from the hook and reverted saveProject() --- src/features/profile-setup/hooks/forms.ts | 43 +++++++++-------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 3b39a95a..30abbc39 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -1,12 +1,11 @@ import { useCallback, useEffect, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useRouter } from "next/router"; import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; import { ZodError } from "zod"; -import { socialDbContractHooks } from "@/common/contracts/social"; -import { useWallet } from "@/entities/_shared/session"; +import { useSession } from "@/entities/_shared/session"; +import { dispatch } from "@/store"; import { saveProject } from "../models/effects"; import { addFundingSourceSchema, projectEditorSchema } from "../models/schemas"; @@ -16,12 +15,12 @@ import { AddFundingSourceInputs, ProjectEditorInputs } from "../models/types"; export const useProjectEditorForm = (options: { defaultValues?: Partial; onSuccess?: () => void; + isEdit: boolean; }) => { - const router = useRouter(); - const { wallet } = useWallet(); const [submitting, setSubmitting] = useState(false); const [crossFieldErrors, setCrossFieldErrors] = useState>({}); + const { isSignedIn } = useSession(); const form = useForm({ resolver: zodResolver(projectEditorSchema), @@ -76,29 +75,21 @@ export const useProjectEditorForm = (options: { }, [form]); const onSubmit: SubmitHandler = useCallback( - async (formData) => { - if (!wallet) return; - - setSubmitting(true); - - try { - // Project saving logic here - const result = await saveProject(formData, wallet?.accountId?.toString() || ""); - - options.onSuccess?.(); - - if (result.success) { - console.log("Opening wallet for approval..."); - } - - router.push("/profile"); - } catch (error) { - console.error(error); - } finally { - setSubmitting(false); + async (_) => { + // not using form data, using store data provided by form + if (isSignedIn) { + setSubmitting(true); + + saveProject({ isEdit: options.isEdit }).then(async (result) => { + if (result.success) { + console.log("Opening wallet for approval..."); + } else { + dispatch.projectEditor.submissionStatus("pending"); + } + }); } }, - [wallet, options, router], + [isSignedIn, options.isEdit], ); return { From eeaace85b02bec7df8b8640f8973a8c7d0519198 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 14:41:07 +0100 Subject: [PATCH 20/84] reverted saveProject() in effects --- src/features/profile-setup/models/effects.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index cfd9a4b8..3a0de96c 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -13,6 +13,7 @@ import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK } from "@/common/c import { socialDbContractClient } from "@/common/contracts/social"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import deepObjectDiff from "@/common/lib/deepObjectDiff"; +import { store } from "@/store"; import getSocialDataFormat from "../utils/getSocialDataFormat"; @@ -26,10 +27,25 @@ const getSocialData = async (accountId: string) => { } }; -export const saveProject = async (data: any, accountId: string) => { +export const saveProject = async ({ isEdit }: { isEdit: boolean }) => { + const data = store.getState().projectEditor; + + const accountId = data.isDao ? data.daoAddress : data.accountId; + + if (!accountId) { + return { success: false, error: "No accountId provided" }; + } + // If Dao, get dao policy const daoPolicy = data.isDao ? await getDaoPolicy(accountId) : null; + // Validate DAO Address + const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; + + if (!isDaoAddressValid) { + return { success: false, error: "DAO: Invalid NEAR account Id" }; + } + // Social Data Format const socialData = getSocialDataFormat(data); @@ -69,7 +85,7 @@ export const saveProject = async (data: any, accountId: string) => { let daoTransactions: Transaction[] = []; // if this is a creation action, we need to add the registry - if (!data.isEdit) { + if (!isEdit) { transactions.push( // lists.potlock.near buildTransaction("register_batch", { From 995ad3e3fb293f118b740909e544e69e82fc8c6a Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 15:37:05 +0100 Subject: [PATCH 21/84] updated input value to the more reactive "values" --- src/features/profile-setup/hooks/forms.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 30abbc39..d5a82eb2 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -70,9 +70,9 @@ export const useProjectEditorForm = (options: { ); const addRepository = useCallback(() => { - const currentRepos = form.getValues("githubRepositories") || []; + const currentRepos = values.githubRepositories || []; form.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true }); - }, [form]); + }, [form, values.githubRepositories]); const onSubmit: SubmitHandler = useCallback( async (_) => { From 3d0acf6cecf97f80b6f6b3d4c3f20e072e89b59d Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 15:38:02 +0100 Subject: [PATCH 22/84] updating to the more recent useSession --- src/pages/edit-project/[projectId].tsx | 56 +++++++++++++------------- src/pages/register.tsx | 8 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/pages/edit-project/[projectId].tsx b/src/pages/edit-project/[projectId].tsx index b457d3c8..a4160c71 100644 --- a/src/pages/edit-project/[projectId].tsx +++ b/src/pages/edit-project/[projectId].tsx @@ -1,42 +1,42 @@ import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSession } from "@/entities/_shared/session"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; -import { useGlobalStoreSelector } from "@/store"; export default function EditProjectPage() { - const { isAuthenticated } = useSessionReduxStore(); + const { isSignedIn, accountId } = useSession(); useInitProjectState(); - // state used to show spinner during the data post - const { submissionStatus, checkRegistrationStatus, checkPreviousProjectDataStatus } = - useGlobalStoreSelector((state) => state.projectEditor); - - const showSpinner = isAuthenticated - ? submissionStatus === "sending" || - checkRegistrationStatus !== "ready" || - checkPreviousProjectDataStatus !== "ready" - : false; - return ( -
-

- {"Edit Project"} -

+ {!isSignedIn ? ( +
+

You need to be athenticated blah blah blah

+ +
+ ) : ( + <> +
+

+ {"Edit Project"} +

-

- {"Create a profile for your project to receive donations and qualify for funding rounds."} -

-
+

+ { + "Create a profile for your project to receive donations and qualify for funding rounds." + } +

+
- {showSpinner && } - + {!isSignedIn && } + {accountId && } + + )}
); } diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 21b9c144..4bcf5c8d 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,18 +1,18 @@ import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSession } from "@/entities/_shared/session"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; export default function RegisterPage() { - const { isAuthenticated } = useSessionReduxStore(); + const { accountId = "", isSignedIn } = useSession(); useInitProjectState(); // state used to show spinner during the data post const { submissionStatus, checkRegistrationStatus, checkPreviousProjectDataStatus } = useGlobalStoreSelector((state) => state.projectEditor); - const showSpinner = isAuthenticated + const showSpinner = isSignedIn ? submissionStatus === "sending" || checkRegistrationStatus !== "ready" || checkPreviousProjectDataStatus !== "ready" @@ -36,7 +36,7 @@ export default function RegisterPage() { {showSpinner && } - + ); } From aec392faba94a1b66f89c386011062a544cba350 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 16:02:08 +0100 Subject: [PATCH 23/84] updated hook --- src/features/profile-setup/hooks/forms.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index d5a82eb2..f4fe10e3 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -80,13 +80,18 @@ export const useProjectEditorForm = (options: { if (isSignedIn) { setSubmitting(true); - saveProject({ isEdit: options.isEdit }).then(async (result) => { - if (result.success) { - console.log("Opening wallet for approval..."); - } else { - dispatch.projectEditor.submissionStatus("pending"); - } - }); + saveProject({ isEdit: options.isEdit }) + .then(async (result) => { + if (result.success) { + console.log("Opening wallet for approval..."); + } else { + dispatch.projectEditor.submissionStatus("pending"); + console.log("error while saving"); + } + }) + .catch((error) => { + console.error(error); + }); } }, [isSignedIn, options.isEdit], From 2eb8ba4c3fce61bfc415ebcd41f55f68d2bd7810 Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 16:13:57 +0100 Subject: [PATCH 24/84] removed unused code and imports --- .../components/ProjectEditor.tsx | 70 ++++--------------- 1 file changed, 12 insertions(+), 58 deletions(-) diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index 4f4711b7..d3d581b7 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -2,19 +2,15 @@ import { FC, useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; -import { prop } from "remeda"; -import { socialDbContractHooks } from "@/common/contracts/social"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, useAccountSocialProfile, } from "@/entities/_shared/account"; -import { useWallet } from "@/entities/_shared/session"; -import { useSessionReduxStore } from "@/entities/_shared/session/hooks/redux-store"; +import { useSession } from "@/entities/_shared/session"; import { rootPathnames } from "@/pathnames"; -import { dispatch, useGlobalStoreSelector } from "@/store"; import AddFundingSourceModal from "./AddFundingSourceModal"; import AddTeamMembersModal from "./AddTeamMembersModal"; @@ -38,7 +34,6 @@ import { LowerBannerContainer, LowerBannerContainerLeft } from "./styles"; import SubHeader from "./SubHeader"; import SuccessfulRegister from "./SuccessfulRegister"; import { useProjectEditorForm } from "../hooks/forms"; -import { useProjectEditorState } from "../models"; import { ProjectEditorInputs } from "../models/types"; interface ProjectEditorProps { @@ -48,7 +43,6 @@ interface ProjectEditorProps { export const ProjectEditor: FC = ({ accountId }) => { const router = useRouter(); const isNewAccount = accountId === undefined; - // const { wallet, isWalletReady } = useWallet(); // // const [editContractIndex, setEditContractIndex] = useState(); // const [initialNameSet, setInitialNameSet] = useState(false); @@ -58,6 +52,8 @@ export const ProjectEditor: FC = ({ accountId }) => { const [editFundingIndex, setEditFundingIndex] = useState(); const [editContractIndex, setEditContractIndex] = useState(); + const sessionData = useSession(); + const { profile: socialDbSnapshot, avatarSrc, @@ -67,8 +63,6 @@ export const ProjectEditor: FC = ({ accountId }) => { enabled: !isNewAccount, }); - // const stateData = useProjectEditorState(); - const defaultValues = useMemo>( () => socialDbSnapshot === undefined @@ -99,7 +93,6 @@ export const ProjectEditor: FC = ({ accountId }) => { updateCategories, updateRepositories, addRepository, - resetForm, errors, } = useProjectEditorForm({ defaultValues, @@ -114,6 +107,11 @@ export const ProjectEditor: FC = ({ accountId }) => { }); }, [defaultValues]); + useEffect(() => { + // Set initial focus to name input. + form.setFocus("name"); + }, []); + const stringifiedDefaultValues = JSON.stringify(defaultValues); const stringifiedValues = JSON.stringify(values); @@ -145,9 +143,6 @@ export const ProjectEditor: FC = ({ accountId }) => { // resetForm(); // }, [router, resetForm]); - const projectTemplate = useGlobalStoreSelector(prop("projectEditor")); - const { isAuthenticated } = useSessionReduxStore(); - // Add loading state const [isDataLoading, setIsDataLoading] = useState(true); @@ -156,47 +151,6 @@ export const ProjectEditor: FC = ({ accountId }) => { return ; } - // const values = form.watch(); - - // useEffect(() => { - // // Set initial focus to name input. - // form.setFocus("name"); - // }, [form]); - - // Set default values by profile - // useEffect(() => form.reset(values), [form, values]); - - // Set initial name - // const [initialNameSet, setInitialNameSet] = useState(false); - - // useEffect(() => { - // if (!initialNameSet && values.name) { - // form.setValue("name", values.name); - // form.trigger(); // re-validate - // setInitialNameSet(true); - // } - // }, [initialNameSet, values.name, form]); - - // // Store description, public good reason and daoAddress - // useEffect(() => { - // if (values.name) { - // dispatch.projectEditor.setProjectName(values.name); - // } - - // if (values.description) { - // dispatch.projectEditor.updateDescription(values.description); - // } - - // if (values.publicGoodReason) { - // dispatch.projectEditor.updatePublicGoodReason(values.publicGoodReason); - // } - // }, [values.description, values.publicGoodReason, values.name]); - - // const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); - // const [addFundingModalOpen, setAddFundingModalOpen] = useState(false); - // const [editFundingIndex, setEditFundingIndex] = useState(); // controls if a funding is being edited - // const [editContractIndex, setEditContractIndex] = useState(); - const getProjectEditorText = () => { if (socialDbSnapshot) { return values.isDao ? "Add proposal to update project" : "Update your project"; @@ -209,10 +163,10 @@ export const ProjectEditor: FC = ({ accountId }) => { const isRepositoriesValid = values.githubRepositories && values.githubRepositories.length > 0; - // // Wait for wallet - // if (!isWalletReady || !wallet) { - // return ; - // } + // Wait for wallet + if (sessionData.isMetadataLoading) { + return ; + } // if (isAuthenticated && values.checkPreviousProjectDataStatus !== "ready") { // return ; From 58641dd52d8ae0c7a139157c6176df20d355cc41 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Tue, 21 Jan 2025 15:46:25 +0000 Subject: [PATCH 25/84] wip --- src/common/viewer/components.tsx | 2 +- src/common/viewer/hooks.ts | 8 +++-- src/common/viewer/internal/wallet-context.ts | 27 +++++++++------- .../viewer/internal/wallet-provider.tsx | 32 +++++++++++-------- src/features/donation/hooks/forms.ts | 8 ++--- src/layout/pot/components/layout.tsx | 4 +-- src/layout/profile/components/layout.tsx | 4 +-- 7 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx index 2c75ea77..c6840e2d 100644 --- a/src/common/viewer/components.tsx +++ b/src/common/viewer/components.tsx @@ -19,5 +19,5 @@ export const ViewerSessionProvider: React.FC = ({ }) => { const isCsr = useMemo(isClient, []); - return isCsr ? {children} : children; + return isCsr ? {children} : <>{ssrFallback ?? children}; }; diff --git a/src/common/viewer/hooks.ts b/src/common/viewer/hooks.ts index 1529cb19..4006e730 100644 --- a/src/common/viewer/hooks.ts +++ b/src/common/viewer/hooks.ts @@ -1,4 +1,4 @@ -import { useContext, useMemo } from "react"; +import { useMemo } from "react"; import { prop } from "remeda"; @@ -7,7 +7,7 @@ import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/ import { isAccountId } from "@/common/lib"; import { useGlobalStoreSelector } from "@/store"; -import { WalletContext } from "./internal/wallet-context"; +import { useWalletContextStore } from "./internal/wallet-context"; import { ViewerSession } from "./types"; /** @@ -15,7 +15,7 @@ import { ViewerSession } from "./types"; * Ensure the consuming layout is wrapped in `ViewerSessionProvider` on the topmost level. */ export const useViewerSession = (): ViewerSession => { - const wallet = useContext(WalletContext); + const wallet = useWalletContextStore(); const { actAsDao } = useGlobalStoreSelector(prop("nav")); const isDaoRepresentative = actAsDao.toggle && Boolean(actAsDao.defaultAddress); @@ -38,6 +38,8 @@ export const useViewerSession = (): ViewerSession => { listId: PUBLIC_GOODS_REGISTRY_LIST_ID, }); + console.log("WALLET", wallet); + return useMemo(() => { if (wallet.isReady && wallet.isSignedIn && accountId) { return { diff --git a/src/common/viewer/internal/wallet-context.ts b/src/common/viewer/internal/wallet-context.ts index 78283acf..6a15e652 100644 --- a/src/common/viewer/internal/wallet-context.ts +++ b/src/common/viewer/internal/wallet-context.ts @@ -1,30 +1,33 @@ -import { createContext } from "react"; - import { create } from "zustand"; import type { AccountId } from "@/common/types"; -type WalletContextState = +type WalletAccountState = { + accountId: AccountId | undefined; + isSignedIn: boolean; +}; + +type WalletContextState = { error: null | unknown } & ( | { isReady: false; isSignedIn: false; accountId: undefined } - | { isReady: true; isSignedIn: boolean; accountId?: AccountId }; + | ({ isReady: true } & WalletAccountState) +); const initialWalletContextState: WalletContextState = { isReady: false, isSignedIn: false, accountId: undefined, + error: null, }; -export const WalletContext = createContext(initialWalletContextState); - type WalletContextStore = WalletContextState & { - initialize: (isReady: boolean) => void; - setIsSignedIn: (isSignedIn: boolean) => void; - setAccountId: (accountId: AccountId | undefined) => void; + registerInit: (isReady: boolean) => void; + setAccountState: (state: WalletAccountState) => void; + setError: (error: unknown) => void; }; export const useWalletContextStore = create((set) => ({ ...initialWalletContextState, - initialize: (isReady: boolean) => set(isReady ? { isReady } : initialWalletContextState), - setIsSignedIn: (isSignedIn: boolean) => set({ isSignedIn }), - setAccountId: (accountId: AccountId | undefined) => set({ accountId }), + registerInit: (isReady: boolean) => set(isReady ? { isReady } : initialWalletContextState), + setAccountState: (newAccountState: WalletAccountState) => set(newAccountState), + setError: (error: unknown) => set({ error }), })); diff --git a/src/common/viewer/internal/wallet-provider.tsx b/src/common/viewer/internal/wallet-provider.tsx index b232f771..446086a9 100644 --- a/src/common/viewer/internal/wallet-provider.tsx +++ b/src/common/viewer/internal/wallet-provider.tsx @@ -1,36 +1,40 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect } from "react"; import { nearClient } from "@/common/api/near"; -import { WalletContext, useWalletContextStore } from "./wallet-context"; +import { useWalletContextStore } from "./wallet-context"; export type WalletProviderProps = { children: React.ReactNode; }; export const WalletProvider: React.FC = ({ children }) => { - const [error, setError] = useState(null); - const { initialize, setIsSignedIn, setAccountId, ...contextState } = useWalletContextStore(); + const { registerInit, setAccountState, setError, isReady, isSignedIn, accountId, error } = + useWalletContextStore(); const syncWalletState = useCallback(() => { - setIsSignedIn(nearClient.walletApi.walletSelector.isSignedIn()); - setAccountId(nearClient.walletApi.accountId); - }, [setAccountId, setIsSignedIn]); + const isWalletSignedIn = nearClient.walletApi.walletSelector.isSignedIn(); + const walletAccountId = nearClient.walletApi.accountId; + + if (isWalletSignedIn !== isSignedIn || walletAccountId !== accountId) { + setAccountState({ accountId: walletAccountId, isSignedIn: isWalletSignedIn }); + } + }, [accountId, isSignedIn, setAccountState]); useEffect(() => { - if (!contextState.isReady && error === null) { + if (!isReady && error === null) { nearClient.walletApi .initNear() - .then(() => initialize(true)) + .then(() => registerInit(true)) .catch((error) => { console.log(error); setError(error); }); } - }, [error, initialize, contextState.isReady]); + }, [error, isReady, registerInit, setError]); useEffect(() => { - if (contextState.isReady) { + if (isReady) { syncWalletState(); nearClient.walletApi.walletSelector.on("signedIn", syncWalletState); @@ -41,7 +45,7 @@ export const WalletProvider: React.FC = ({ children }) => { } return () => { - if (contextState.isReady) { + if (isReady) { nearClient.walletApi.walletSelector.off("signedIn", syncWalletState); nearClient.walletApi.walletSelector.off("signedOut", syncWalletState); nearClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); @@ -49,7 +53,7 @@ export const WalletProvider: React.FC = ({ children }) => { nearClient.walletApi.walletSelector.off("uriChanged", syncWalletState); } }; - }, [syncWalletState, contextState.isReady]); + }, [syncWalletState, isReady]); - return {children}; + return children; }; diff --git a/src/features/donation/hooks/forms.ts b/src/features/donation/hooks/forms.ts index fc4139ec..8443cb2f 100644 --- a/src/features/donation/hooks/forms.ts +++ b/src/features/donation/hooks/forms.ts @@ -27,6 +27,7 @@ export type DonationFormParams = DonationAllocationKey & { }; export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormParams) => { + const now = Temporal.Now.instant(); const viewer = useViewerSession(); const isSingleProjectDonation = "accountId" in params; const isPotDonation = "potId" in params; @@ -44,10 +45,9 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa }); const matchingPots = recipientActivePots.filter( - ({ matching_round_start }) => - Temporal.Now.instant() - .since(Temporal.Instant.from(matching_round_start)) - .total("milliseconds") > 0, + ({ matching_round_start, matching_round_end }) => + now.since(Temporal.Instant.from(matching_round_start)).total("milliseconds") > 0 && + now.until(Temporal.Instant.from(matching_round_end)).total("milliseconds") > 0, ); const defaultPotAccountId = useMemo( diff --git a/src/layout/pot/components/layout.tsx b/src/layout/pot/components/layout.tsx index f894edef..0b9acca3 100644 --- a/src/layout/pot/components/layout.tsx +++ b/src/layout/pot/components/layout.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { PageWithBanner } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { ViewerSessionProvider } from "@/common/viewer"; import { ChallengeModal } from "@/entities/pot"; @@ -38,7 +38,7 @@ export const PotLayout: React.FC = ({ children }) => { const openChallengeModal = useCallback(() => setChallengeModalOpen(true), []); return ( - }> + {/** * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index a129b51a..531deba2 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { useRegistration } from "@/common/_deprecated/useRegistration"; -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { ViewerSessionProvider } from "@/common/viewer"; import { ProjectBanner } from "@/entities/project"; @@ -152,7 +152,7 @@ export const ProfileLayout: React.FC = ({ children }) => { const isProject = isRegisteredProject; return ( - }> + {isProject && } From a44510f0386058f6b00bc597913299e27a589639 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Tue, 21 Jan 2025 16:23:39 +0000 Subject: [PATCH 26/84] wip --- src/common/constants.ts | 2 + src/common/contracts/core/lists/hooks.ts | 5 +- src/common/contracts/core/pot/hooks.ts | 9 +-- src/common/contracts/core/voting/hooks.ts | 64 +++++++++++-------- .../ref-finance/ref-exchange/hooks.ts | 9 ++- src/common/contracts/social/hooks.ts | 3 +- src/common/contracts/tokens/ft/hooks.ts | 7 +- src/common/viewer/components.tsx | 8 +-- src/layout/campaign/components/layout.tsx | 5 +- src/pages/campaign/create.tsx | 2 +- src/pages/campaigns.tsx | 6 +- src/pages/deploy.tsx | 2 +- src/pages/edit-project/[projectId].tsx | 6 +- src/pages/feed/[account]/[block]/index.tsx | 6 +- src/pages/feed/index.tsx | 7 +- src/pages/list/create/index.tsx | 2 +- src/pages/list/duplicate/[id].tsx | 2 +- src/pages/list/edit/[id]/index.tsx | 2 +- src/pages/register.tsx | 6 +- 19 files changed, 77 insertions(+), 76 deletions(-) diff --git a/src/common/constants.ts b/src/common/constants.ts index ea3ba782..59915c0e 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -6,6 +6,8 @@ import { Metadata } from "next"; import { NETWORK, PLATFORM_NAME } from "./_config"; import { ChronologicalSortOrderVariant, type TokenId } from "./types"; +export const IS_CLIENT = typeof window !== "undefined"; + export const DEBUG = process.env.NEXT_PUBLIC_DEBUG === "true" ? true : false; /** diff --git a/src/common/contracts/core/lists/hooks.ts b/src/common/contracts/core/lists/hooks.ts index 435831cd..bcf39ea6 100644 --- a/src/common/contracts/core/lists/hooks.ts +++ b/src/common/contracts/core/lists/hooks.ts @@ -1,5 +1,6 @@ import useSWR from "swr"; +import { IS_CLIENT } from "@/common/constants"; import type { ByAccountId, ByListId, ConditionalActivation } from "@/common/types"; import * as contractClient from "./client"; @@ -16,7 +17,7 @@ export const useIsRegistered = ({ useSWR( ["useIsRegistered", accountId, listId, params], ([_queryKey, accountIdKey, listIdKey, paramsKey]) => - !enabled + !enabled || !IS_CLIENT ? undefined : contractClient.is_registered({ account_id: accountIdKey, @@ -31,7 +32,7 @@ export const useRegistration = ({ listId, }: ByAccountId & ByListId & ConditionalActivation) => useSWR(["useRegistration", accountId, listId], ([_queryKey, accountIdKey, listIdKey]) => - !enabled + !enabled || !IS_CLIENT ? undefined : contractClient.getRegistration({ registrant_id: accountIdKey, list_id: listIdKey }), ); diff --git a/src/common/contracts/core/pot/hooks.ts b/src/common/contracts/core/pot/hooks.ts index a9bd7247..d05ce8db 100644 --- a/src/common/contracts/core/pot/hooks.ts +++ b/src/common/contracts/core/pot/hooks.ts @@ -1,26 +1,27 @@ import useSWR from "swr"; import type { ByPotId } from "@/common/api/indexer"; +import { IS_CLIENT } from "@/common/constants"; import type { ConditionalActivation } from "@/common/types"; import * as contractClient from "./client"; export const useConfig = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => useSWR(["useConfig", potId], ([_queryKey, potIdKey]) => - !enabled ? undefined : contractClient.get_config({ potId: potIdKey }), + !enabled || !IS_CLIENT ? undefined : contractClient.get_config({ potId: potIdKey }), ); export const useApplications = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => useSWR(["useApplications", potId], ([_queryKey, potIdKey]) => - !enabled ? undefined : contractClient.get_applications({ potId: potIdKey }), + !enabled || !IS_CLIENT ? undefined : contractClient.get_applications({ potId: potIdKey }), ); export const usePayouts = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => useSWR(["usePayouts", potId], ([_queryKey, potIdKey]) => - !enabled ? undefined : contractClient.get_payouts({ potId: potIdKey }), + !enabled || !IS_CLIENT ? undefined : contractClient.get_payouts({ potId: potIdKey }), ); export const usePayoutChallenges = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => useSWR(["usePayoutChallenges", potId], ([_queryKey, potIdKey]) => - !enabled ? undefined : contractClient.get_payouts_challenges({ potId: potIdKey }), + !enabled || !IS_CLIENT ? undefined : contractClient.get_payouts_challenges({ potId: potIdKey }), ); diff --git a/src/common/contracts/core/voting/hooks.ts b/src/common/contracts/core/voting/hooks.ts index 2822eda4..cda2e2db 100644 --- a/src/common/contracts/core/voting/hooks.ts +++ b/src/common/contracts/core/voting/hooks.ts @@ -1,6 +1,7 @@ import useSWR from "swr"; import type { ByPotId } from "@/common/api/indexer"; +import { IS_CLIENT } from "@/common/constants"; import { ByAccountId, type ConditionalActivation } from "@/common/types"; import { AccountId, ElectionId } from "./interfaces"; @@ -13,95 +14,105 @@ export interface ByElectionId { type BasicElectionQueryKey = ByElectionId & ConditionalActivation; export const useElections = ({ enabled = true }: ConditionalActivation | undefined = {}) => - useSWR(["get_elections"], () => (!enabled ? undefined : votingContractClient.get_elections({}))); + useSWR(["get_elections"], () => + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_elections({}), + ); -export const useActiveElections = () => - useSWR(["get_active_elections"], () => votingContractClient.get_active_elections()); +export const useActiveElections = ({ enabled = true }: ConditionalActivation | undefined = {}) => + useSWR(["get_active_elections"], () => + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_active_elections(), + ); -export const useElection = ({ electionId, enabled = true }: BasicElectionQueryKey) => +export const useElection = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR( ["get_election", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled ? undefined : votingContractClient.get_election({ election_id }), + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election({ election_id }), ); -export const useIsVotingPeriod = ({ electionId, enabled = true }: BasicElectionQueryKey) => +export const useIsVotingPeriod = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR( ["is_voting_period", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled ? undefined : votingContractClient.is_voting_period({ election_id }), + !enabled || !IS_CLIENT ? undefined : votingContractClient.is_voting_period({ election_id }), ); -export const useElectionCandidates = ({ electionId, enabled = true }: BasicElectionQueryKey) => +export const useElectionCandidates = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR( ["get_election_candidates", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled ? undefined : votingContractClient.get_election_candidates({ election_id }), + !enabled || !IS_CLIENT + ? undefined + : votingContractClient.get_election_candidates({ election_id }), ); export const useElectionCandidateVotes = ({ + enabled = true, electionId, accountId, - enabled = true, }: BasicElectionQueryKey & ByAccountId) => useSWR( ["get_candidate_votes", electionId, accountId], ([_queryKey, election_id, candidate_id]: [string, ElectionId, AccountId]) => - !enabled + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_candidate_votes({ election_id, candidate_id }), ); -export const useElectionVotes = ({ electionId, enabled = true }: BasicElectionQueryKey) => +export const useElectionVotes = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR(["get_election_votes", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled ? undefined : votingContractClient.get_election_votes({ election_id }), + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election_votes({ election_id }), ); -export const useElectionVoteCount = ({ electionId, enabled = true }: BasicElectionQueryKey) => +export const useElectionVoteCount = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR( ["get_election_vote_count", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled ? undefined : votingContractClient.get_election_vote_count({ election_id }), + !enabled || !IS_CLIENT + ? undefined + : votingContractClient.get_election_vote_count({ election_id }), ); export const useVotingRoundVoterVotes = ({ + enabled = true, electionId, accountId, - enabled = true, }: BasicElectionQueryKey & ByAccountId) => useSWR( ["get_voter_votes", electionId, accountId], ([_queryKey, election_id, voter]: [string, ElectionId, AccountId]) => - !enabled ? undefined : votingContractClient.get_voter_votes({ election_id, voter }), + !enabled || !IS_CLIENT + ? undefined + : votingContractClient.get_voter_votes({ election_id, voter }), ); export const useVoterRemainingCapacity = ({ + enabled = true, electionId, accountId, - enabled = true, }: BasicElectionQueryKey & ByAccountId) => useSWR( ["get_voter_remaining_capacity", electionId, accountId], ([_queryKey, election_id, voter]: [string, ElectionId, AccountId]) => - !enabled + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_voter_remaining_capacity({ election_id, voter }), ); -export const useUniqueVoters = ({ electionId, enabled = true }: BasicElectionQueryKey) => +export const useUniqueVoters = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR(["get_unique_voters", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled ? undefined : votingContractClient.get_unique_voters({ election_id }), + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_unique_voters({ election_id }), ); -export const usePotElections = ({ potId, enabled = true }: ByPotId & ConditionalActivation) => { - const { data: elections, isLoading } = useElections(); +export const usePotElections = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => { + const { data: elections, isLoading } = useElections({ enabled }); return { isLoading, @@ -113,8 +124,11 @@ export const usePotElections = ({ potId, enabled = true }: ByPotId & Conditional }; }; -export const useActivePotElections = ({ potId }: ByPotId) => { - const { data: activeElections } = useActiveElections(); +export const useActivePotElections = ({ + enabled = true, + potId, +}: ByPotId & ConditionalActivation) => { + const { data: activeElections } = useActiveElections({ enabled }); return { activeElections: activeElections?.filter( diff --git a/src/common/contracts/ref-finance/ref-exchange/hooks.ts b/src/common/contracts/ref-finance/ref-exchange/hooks.ts index 30f8e69f..3d0e24e5 100644 --- a/src/common/contracts/ref-finance/ref-exchange/hooks.ts +++ b/src/common/contracts/ref-finance/ref-exchange/hooks.ts @@ -1,6 +1,11 @@ import useSWR from "swr"; +import { IS_CLIENT } from "@/common/constants"; +import type { ConditionalActivation } from "@/common/types"; + import * as client from "./client"; -export const useWhitelistedTokens = () => - useSWR(["get_whitelisted_tokens"], () => client.get_whitelisted_tokens()); +export const useWhitelistedTokens = ({ enabled = true }: ConditionalActivation | undefined = {}) => + useSWR(["get_whitelisted_tokens"], () => + !enabled || !IS_CLIENT ? undefined : client.get_whitelisted_tokens(), + ); diff --git a/src/common/contracts/social/hooks.ts b/src/common/contracts/social/hooks.ts index 3b132f38..90225de7 100644 --- a/src/common/contracts/social/hooks.ts +++ b/src/common/contracts/social/hooks.ts @@ -1,5 +1,6 @@ import useSWR from "swr"; +import { IS_CLIENT } from "@/common/constants"; import type { ByAccountId, ConditionalActivation } from "@/common/types"; import * as contractClient from "./client"; @@ -9,7 +10,7 @@ export const useSocialProfile = ({ accountId, }: ByAccountId & ConditionalActivation) => useSWR(["useSocialProfile", accountId], ([_queryKey, account_id]) => - !enabled + !enabled || !IS_CLIENT ? undefined : contractClient .getSocialProfile({ accountId: account_id }) diff --git a/src/common/contracts/tokens/ft/hooks.ts b/src/common/contracts/tokens/ft/hooks.ts index e7ec5320..572bbc11 100644 --- a/src/common/contracts/tokens/ft/hooks.ts +++ b/src/common/contracts/tokens/ft/hooks.ts @@ -1,21 +1,24 @@ import useSWR from "swr"; +import { IS_CLIENT } from "@/common/constants"; import type { ByAccountId, ByTokenId, WithDisabled } from "@/common/types"; import * as ftClient from "./client"; +// TODO: Use conventional `enabled` instead of `disabled` export const useFtMetadata = ({ disabled = false, ...params }: ByTokenId & WithDisabled) => useSWR( - () => (disabled ? null : ["ft_metadata", params.tokenId]), + () => (disabled || !IS_CLIENT ? null : ["ft_metadata", params.tokenId]), ([_queryKey, tokenId]) => ftClient.ft_metadata({ tokenId }).catch(() => undefined), ); +// TODO: Use conventional `enabled` instead of `disabled` export const useFtBalanceOf = ({ disabled = false, ...params }: ByAccountId & ByTokenId & WithDisabled) => useSWR( - () => (disabled ? null : ["ft_balance_of", params.accountId, params.tokenId]), + () => (disabled || !IS_CLIENT ? null : ["ft_balance_of", params.accountId, params.tokenId]), ([_queryKey, accountId, tokenId]) => ftClient.ft_balance_of({ accountId, tokenId }).catch(() => undefined), diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx index c6840e2d..19955f7a 100644 --- a/src/common/viewer/components.tsx +++ b/src/common/viewer/components.tsx @@ -6,18 +6,14 @@ import { WalletProvider } from "./internal/wallet-provider"; export type ViewerSessionProviderProps = { children: React.ReactNode; - ssrFallback?: React.ReactNode; }; /** * Required for session bindings to be available on the client. * On the server, the ssrFallback is rendered instead. */ -export const ViewerSessionProvider: React.FC = ({ - children, - ssrFallback, -}) => { +export const ViewerSessionProvider: React.FC = ({ children }) => { const isCsr = useMemo(isClient, []); - return isCsr ? {children} : <>{ssrFallback ?? children}; + return isCsr ? {children} : children; }; diff --git a/src/layout/campaign/components/layout.tsx b/src/layout/campaign/components/layout.tsx index dd82b3d7..e6233de3 100644 --- a/src/layout/campaign/components/layout.tsx +++ b/src/layout/campaign/components/layout.tsx @@ -3,7 +3,7 @@ import { useCallback, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { cn } from "@/common/ui/utils"; import { ViewerSessionProvider } from "@/common/viewer"; @@ -92,7 +92,6 @@ type ReactLayoutProps = { export const CampaignLayout: React.FC = ({ children }) => { const router = useRouter(); const pathname = router.pathname; - const tabs = CAMPAIGN_TAB_ROUTES; const [selectedTab, setSelectedTab] = useState( @@ -105,7 +104,7 @@ export const CampaignLayout: React.FC = ({ children }) => { ); return ( - }> +
diff --git a/src/pages/campaign/create.tsx b/src/pages/campaign/create.tsx index 2ee13961..5555f2bd 100644 --- a/src/pages/campaign/create.tsx +++ b/src/pages/campaign/create.tsx @@ -19,7 +19,7 @@ export default function CreateCampaign() {
- }> + diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index d47f8bc3..b59519d0 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -31,9 +31,5 @@ export default function CampaignsPage() { } CampaignsPage.getLayout = function getLayout(page: React.ReactNode) { - return ( - }> - {page} - - ); + return {page}; }; diff --git a/src/pages/deploy.tsx b/src/pages/deploy.tsx index 1397e899..b447a845 100644 --- a/src/pages/deploy.tsx +++ b/src/pages/deploy.tsx @@ -11,7 +11,7 @@ export default function PotDeployPage() { usePotDeploymentSuccessMiddleware(); return ( - }> +
}> - {page} - - ); + return {page}; }; diff --git a/src/pages/feed/[account]/[block]/index.tsx b/src/pages/feed/[account]/[block]/index.tsx index a38219c5..d50f76b7 100644 --- a/src/pages/feed/[account]/[block]/index.tsx +++ b/src/pages/feed/[account]/[block]/index.tsx @@ -112,9 +112,5 @@ export default function FeedAccountBlockPostPage() { } FeedAccountBlockPostPage.getLayout = function getLayout(page: React.ReactNode) { - return ( - }> - {page} - - ); + return {page}; }; diff --git a/src/pages/feed/index.tsx b/src/pages/feed/index.tsx index 2c26df08..818fec68 100644 --- a/src/pages/feed/index.tsx +++ b/src/pages/feed/index.tsx @@ -5,7 +5,6 @@ import InfiniteScrollWrapper from "react-infinite-scroll-component"; import { indexer } from "@/common/api/indexer"; import { fetchGlobalFeeds } from "@/common/api/near-social"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { SplashScreen } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { ViewerSessionProvider } from "@/common/viewer"; import { PostCard } from "@/entities/post"; @@ -112,9 +111,5 @@ export default function FeedPage() { } FeedPage.getLayout = function getLayout(page: React.ReactNode) { - return ( - }> - {page} - - ); + return {page}; }; diff --git a/src/pages/list/create/index.tsx b/src/pages/list/create/index.tsx index 67e480b1..a6e18671 100644 --- a/src/pages/list/create/index.tsx +++ b/src/pages/list/create/index.tsx @@ -7,7 +7,7 @@ export default function Page() { - }> + diff --git a/src/pages/list/duplicate/[id].tsx b/src/pages/list/duplicate/[id].tsx index c56e0dfa..3a5f1e59 100644 --- a/src/pages/list/duplicate/[id].tsx +++ b/src/pages/list/duplicate/[id].tsx @@ -7,7 +7,7 @@ export default function DuplicateList() { - }> + diff --git a/src/pages/list/edit/[id]/index.tsx b/src/pages/list/edit/[id]/index.tsx index 8b4a19e0..d3727198 100644 --- a/src/pages/list/edit/[id]/index.tsx +++ b/src/pages/list/edit/[id]/index.tsx @@ -9,7 +9,7 @@ export default function Page() { - }> + diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 756686da..02c4e06b 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -42,9 +42,5 @@ export default function RegisterPage() { } RegisterPage.getLayout = function getLayout(page: React.ReactNode) { - return ( - }> - {page} - - ); + return {page}; }; From 5053e3266565dde12c7ce157471189706d10d6f4 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Tue, 21 Jan 2025 16:51:01 +0000 Subject: [PATCH 27/84] wip --- .vscode/settings.json | 1 + src/common/ui/components/index.ts | 1 + src/common/viewer/hooks.ts | 4 - .../campaign/components/CampaignBanner.tsx | 128 ++++++++++++-- ...Campaigns.tsx => CampaignCarouselItem.tsx} | 61 +------ .../components/SingleCampaignBanner.tsx | 117 ------------- src/entities/campaign/index.ts | 4 +- src/layout/campaign/components/layout.tsx | 29 ++-- src/layout/components/app-bar.tsx | 8 +- src/layout/pot/components/layout.tsx | 159 +++++++++--------- src/layout/profile/components/layout.tsx | 41 +++-- src/pages/_app.tsx | 5 +- src/pages/campaign/create.tsx | 7 +- src/pages/campaigns.tsx | 91 ++++++++-- src/pages/deploy.tsx | 47 +++--- src/pages/edit-project/[projectId].tsx | 8 +- src/pages/feed/[account]/[block]/index.tsx | 6 - 17 files changed, 339 insertions(+), 378 deletions(-) rename src/entities/campaign/components/{FeaturedCampaigns.tsx => CampaignCarouselItem.tsx} (63%) delete mode 100644 src/entities/campaign/components/SingleCampaignBanner.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 2fbb3042..83382908 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -58,6 +58,7 @@ "datetime", "defi", "dontcare", + "extralight", "fastnear", "foodbank", "formkit", diff --git a/src/common/ui/components/index.ts b/src/common/ui/components/index.ts index 7cd04ff4..d7022bdc 100644 --- a/src/common/ui/components/index.ts +++ b/src/common/ui/components/index.ts @@ -47,6 +47,7 @@ export * from "./atoms/filter-chip"; * * See https://atomicdesign.bradfrost.com/chapter-2/#molecules */ +export * from "./molecules/carousel"; export * from "./molecules/checklist"; export * from "./molecules/clipboard-copy-button"; export * from "./molecules/data-loading-placeholder"; diff --git a/src/common/viewer/hooks.ts b/src/common/viewer/hooks.ts index 4006e730..d880483c 100644 --- a/src/common/viewer/hooks.ts +++ b/src/common/viewer/hooks.ts @@ -10,10 +10,6 @@ import { useGlobalStoreSelector } from "@/store"; import { useWalletContextStore } from "./internal/wallet-context"; import { ViewerSession } from "./types"; -/** - * Heads Up! - * Ensure the consuming layout is wrapped in `ViewerSessionProvider` on the topmost level. - */ export const useViewerSession = (): ViewerSession => { const wallet = useWalletContextStore(); const { actAsDao } = useGlobalStoreSelector(prop("nav")); diff --git a/src/entities/campaign/components/CampaignBanner.tsx b/src/entities/campaign/components/CampaignBanner.tsx index 66682ee8..4be82b65 100644 --- a/src/entities/campaign/components/CampaignBanner.tsx +++ b/src/entities/campaign/components/CampaignBanner.tsx @@ -1,25 +1,117 @@ -import Link from "next/link"; +import { useEffect, useState } from "react"; -import { Button } from "@/common/ui/components"; +import { useRouter } from "next/router"; +import { LazyLoadImage } from "react-lazy-load-image-component"; + +import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; +import { Campaign, campaignsContractClient } from "@/common/contracts/core"; +import { yoctoNearToFloat } from "@/common/lib"; +import getTimePassed from "@/common/lib/getTimePassed"; +import { SocialsShare } from "@/common/ui/components/molecules/social-share"; +import { AccountProfileLink } from "@/entities/_shared/account"; +import { DonateToCampaignProjects } from "@/features/donation"; + +import { CampaignProgressBar } from "./CampaignProgressBar"; export const CampaignBanner = () => { + const [campaign, setCampaign] = useState(); + const [loading, setLoading] = useState(false); + + const usdInfo = useNearToUsdWithFallback( + Number(yoctoNearToFloat((campaign?.total_raised_amount as string) || "0")), + ); + + const { + query: { campaignId }, + } = useRouter(); + + useEffect(() => { + if (!campaignId) return; + setLoading(true); + + campaignsContractClient + .get_campaign({ campaign_id: parseInt(campaignId as string) as any }) + .then((response) => { + setCampaign(response); + }) + .catch((err) => console.error(err)) + .finally(() => setLoading(false)); + }, [campaignId]); + + if (loading) { + return
Loading...
; + } + + const isStarted = getTimePassed(Number(campaign?.start_ms), true)?.includes("-"); + + const isEnded = campaign?.end_ms + ? getTimePassed(Number(campaign?.end_ms), false, true)?.includes("-") + : false; + return ( -
-

Fund Your Ideas

-

- Bring your vision to life with a powerful fundraising campaign to support groundbreaking - projects. Reach your goals and make a positive impact on your community{" "} - - Learn more - -

- +
+
+
+ +
{" "} +
+

{campaign?.name}

+
+
+

FOR

+ +
+
+ {" "} +
+
+

ORGANIZED BY

+ +
+
+
+
+

{campaign?.description}

+
+
+
+

+ TOTAL AMOUNT RAISED +

+
+

+ {yoctoNearToFloat(campaign?.total_raised_amount || "0")} NEAR +

+ {campaign?.total_raised_amount &&

{usdInfo}

} +
+
+ +
+ + +
+
); }; diff --git a/src/entities/campaign/components/FeaturedCampaigns.tsx b/src/entities/campaign/components/CampaignCarouselItem.tsx similarity index 63% rename from src/entities/campaign/components/FeaturedCampaigns.tsx rename to src/entities/campaign/components/CampaignCarouselItem.tsx index 72b95bda..e3867c62 100644 --- a/src/entities/campaign/components/FeaturedCampaigns.tsx +++ b/src/entities/campaign/components/CampaignCarouselItem.tsx @@ -1,73 +1,16 @@ -import { useEffect, useState } from "react"; - import Link from "next/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; import { Campaign } from "@/common/contracts/core"; import { truncate, yoctoNearToFloat } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; -import { - Carousel, - CarouselApi, - CarouselContent, - CarouselItem, -} from "@/common/ui/components/molecules/carousel"; +import { CarouselItem } from "@/common/ui/components/molecules/carousel"; import { AccountProfileLink } from "@/entities/_shared/account"; import { DonateToCampaignProjects } from "@/features/donation"; import { CampaignProgressBar } from "./CampaignProgressBar"; -export const FeaturedCampaigns = ({ data }: { data: Campaign[] }) => { - const [api, setApi] = useState(); - const [current, setCurrent] = useState(0); - - useEffect(() => { - if (!api) return; - - setCurrent(api.selectedScrollSnap()); - - api.on("select", () => { - setCurrent(api.selectedScrollSnap()); - }); - }, [api]); - - if (!data?.length) { - return <>; - } - - return ( -
-
-
-

Featured Campaigns

-

{current + 1}/3

-
-
- api?.scrollTo(current - 1)} - className="h-6 w-6 cursor-pointer rounded-full border border-gray-400 text-[14px] text-gray-500" - /> - api?.scrollTo(current + 1)} - className="h-6 w-6 cursor-pointer rounded-full border border-gray-400 text-[14px] text-gray-500" - /> -
-
- - - {data?.length && - data?.slice(0, 3)?.map((data) => )} - - -
- ); -}; - -const FeaturedCampaignCard = ({ data }: { data: Campaign }) => { +export const CampaignCarouselItem = ({ data }: { data: Campaign }) => { const isStarted = getTimePassed(Number(data.start_ms), true)?.includes("-"); const isEnded = data?.end_ms diff --git a/src/entities/campaign/components/SingleCampaignBanner.tsx b/src/entities/campaign/components/SingleCampaignBanner.tsx deleted file mode 100644 index 61622523..00000000 --- a/src/entities/campaign/components/SingleCampaignBanner.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { useEffect, useState } from "react"; - -import { useRouter } from "next/router"; -import { LazyLoadImage } from "react-lazy-load-image-component"; - -import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; -import { Campaign, campaignsContractClient } from "@/common/contracts/core"; -import { yoctoNearToFloat } from "@/common/lib"; -import getTimePassed from "@/common/lib/getTimePassed"; -import { SocialsShare } from "@/common/ui/components/molecules/social-share"; -import { AccountProfileLink } from "@/entities/_shared/account"; -import { DonateToCampaignProjects } from "@/features/donation"; - -import { CampaignProgressBar } from "./CampaignProgressBar"; - -export const SingleCampaignBanner = () => { - const [campaign, setCampaign] = useState(); - const [loading, setLoading] = useState(false); - - const usdInfo = useNearToUsdWithFallback( - Number(yoctoNearToFloat((campaign?.total_raised_amount as string) || "0")), - ); - - const { - query: { campaignId }, - } = useRouter(); - - useEffect(() => { - if (!campaignId) return; - setLoading(true); - - campaignsContractClient - .get_campaign({ campaign_id: parseInt(campaignId as string) as any }) - .then((response) => { - setCampaign(response); - }) - .catch((err) => console.error(err)) - .finally(() => setLoading(false)); - }, [campaignId]); - - if (loading) { - return
Loading...
; - } - - const isStarted = getTimePassed(Number(campaign?.start_ms), true)?.includes("-"); - - const isEnded = campaign?.end_ms - ? getTimePassed(Number(campaign?.end_ms), false, true)?.includes("-") - : false; - - return ( -
-
-
- -
{" "} -
-

{campaign?.name}

-
-
-

FOR

- -
-
- {" "} -
-
-

ORGANIZED BY

- -
-
-
-
-

{campaign?.description}

-
-
-
-

- TOTAL AMOUNT RAISED -

-
-

- {yoctoNearToFloat(campaign?.total_raised_amount || "0")} NEAR -

- {campaign?.total_raised_amount &&

{usdInfo}

} -
-
- -
- - -
-
-
- ); -}; diff --git a/src/entities/campaign/index.ts b/src/entities/campaign/index.ts index e3907636..d06b64bd 100644 --- a/src/entities/campaign/index.ts +++ b/src/entities/campaign/index.ts @@ -1,9 +1,11 @@ -export * from "./components/SingleCampaignBanner"; +export * from "./components/CampaignBanner"; export * from "./components/CampaignCard"; +export * from "./components/CampaignCarouselItem"; export * from "./components/CampaignDonorsTable"; export * from "./components/CampaignForm"; export * from "./components/CampaignProgressBar"; export * from "./components/CampaignSettings"; +export * from "./components/CampaignsList"; export * from "./hooks/redirects"; export * from "./hooks/useCampaign"; diff --git a/src/layout/campaign/components/layout.tsx b/src/layout/campaign/components/layout.tsx index e6233de3..4e1da7f8 100644 --- a/src/layout/campaign/components/layout.tsx +++ b/src/layout/campaign/components/layout.tsx @@ -6,8 +6,7 @@ import { useRouter } from "next/router"; import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider } from "@/common/viewer"; -import { SingleCampaignBanner } from "@/entities/campaign"; +import { CampaignBanner } from "@/entities/campaign"; const CAMPAIGN_TAB_ROUTES: TabOption[] = [ { @@ -104,19 +103,17 @@ export const CampaignLayout: React.FC = ({ children }) => { ); return ( - - -
- -
- handleSelectedTab(tabId)} - /> -
{children}
-
-
+ +
+ +
+ handleSelectedTab(tabId)} + /> +
{children}
+
); }; diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index 8c857b3f..699f4cbb 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -8,7 +8,7 @@ import { NETWORK } from "@/common/_config"; import { nearClient } from "@/common/api/near"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider, useViewerSession } from "@/common/viewer"; +import { useViewerSession } from "@/common/viewer"; import { CartLink } from "@/entities/cart"; import { rootPathnames } from "@/pathnames"; @@ -171,11 +171,7 @@ export const AppBar = () => { {/* Right */}
- - - - - + setShowMobileMenu(!showMobileMenu)} />
diff --git a/src/layout/pot/components/layout.tsx b/src/layout/pot/components/layout.tsx index 0b9acca3..80da4550 100644 --- a/src/layout/pot/components/layout.tsx +++ b/src/layout/pot/components/layout.tsx @@ -6,7 +6,6 @@ import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; import { PageWithBanner } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider } from "@/common/viewer"; import { ChallengeModal } from "@/entities/pot"; import { DonationSybilWarning } from "@/features/donation"; import { MatchingPoolContributionModal } from "@/features/matching-pool-contribution"; @@ -38,94 +37,92 @@ export const PotLayout: React.FC = ({ children }) => { const openChallengeModal = useCallback(() => setChallengeModalOpen(true), []); return ( - - - {/** - * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE - * //! AND MUST BE REPLACED WITH A SIMPLE TOAST CALL - * //! THIS IS THE EXACT ROOT CAUSE OF THE POT TRANSACTION CONFIRMATION BUGS - */} - setSuccessModalOpen(false)} - /> + + {/** + * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE + * //! AND MUST BE REPLACED WITH A SIMPLE TOAST CALL + * //! THIS IS THE EXACT ROOT CAUSE OF THE POT TRANSACTION CONFIRMATION BUGS + */} + setSuccessModalOpen(false)} + /> - {/** - * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE - * //! AND MUST BE REPLACED WITH A SIMPLE TOAST CALL - * //! THIS IS THE EXACT ROOT CAUSE OF THE POT TRANSACTION CONFIRMATION BUGS - */} - setErrorModalOpen(false)} - /> + {/** + * // TODO!: THIS MODAL IS NOT SUPPOSED TO BE REUSABLE + * //! AND MUST BE REPLACED WITH A SIMPLE TOAST CALL + * //! THIS IS THE EXACT ROOT CAUSE OF THE POT TRANSACTION CONFIRMATION BUGS + */} + setErrorModalOpen(false)} + /> - {pot && ( - <> - setFundModalOpen(false)} - /> + {pot && ( + <> + setFundModalOpen(false)} + /> - setApplyModalOpen(false)} - /> + setApplyModalOpen(false)} + /> - setChallengeModalOpen(false)} - {...{ potId }} - /> - - )} + setChallengeModalOpen(false)} + {...{ potId }} + /> + + )} - + - + -
-
- {orderedTabList.map(({ tag, href, isHidden }) => { - return ( - - {tag} - - ); - })} -
+
+
+ {orderedTabList.map(({ tag, href, isHidden }) => { + return ( + + {tag} + + ); + })}
+
- {/* Tab Content */} -
{children}
- - + {/* Tab Content */} +
{children}
+ ); }; diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index 531deba2..2ec6a24a 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -6,7 +6,6 @@ import { useRouter } from "next/router"; import { useRegistration } from "@/common/_deprecated/useRegistration"; import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; -import { ViewerSessionProvider } from "@/common/viewer"; import { ProjectBanner } from "@/entities/project"; import { ProfileLayoutControls } from "./ProfileLayoutControls"; @@ -152,26 +151,24 @@ export const ProfileLayout: React.FC = ({ children }) => { const isProject = isRegisteredProject; return ( - - - {isProject && } - - - - { - setSelectedTab(tabs.find((tabRoute) => tabRoute.id === tabId)!); - }} - /> - - {/* Tab Content */} -
- {children} -
-
-
+ + {isProject && } + + + + { + setSelectedTab(tabs.find((tabRoute) => tabRoute.id === tabId)!); + }} + /> + + {/* Tab Content */} +
+ {children} +
+
); }; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index dcfa3671..898cb287 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -20,6 +20,7 @@ import { APP_METADATA } from "@/common/constants"; import { TooltipProvider } from "@/common/ui/components"; import { Toaster } from "@/common/ui/components/molecules/toaster"; import { cn } from "@/common/ui/utils"; +import { ViewerSessionProvider } from "@/common/viewer"; import { AppBar } from "@/layout/components/app-bar"; import { dispatch, store } from "@/store"; @@ -43,7 +44,7 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) const getLayout = Component.getLayout ?? ((page) => page); return ( - <> + {APP_METADATA.title} @@ -65,6 +66,6 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) - + ); } diff --git a/src/pages/campaign/create.tsx b/src/pages/campaign/create.tsx index 5555f2bd..bf1143b4 100644 --- a/src/pages/campaign/create.tsx +++ b/src/pages/campaign/create.tsx @@ -1,5 +1,4 @@ -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; -import { ViewerSessionProvider } from "@/common/viewer"; +import { PageWithBanner } from "@/common/ui/components"; import { CampaignForm, useCampaignDeploymentRedirect } from "@/entities/campaign"; export default function CreateCampaign() { @@ -19,9 +18,7 @@ export default function CreateCampaign() {
- - - + ); } diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index b59519d0..14b3e9dc 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -1,11 +1,66 @@ import { useEffect, useState } from "react"; +import Link from "next/link"; + import { Campaign, campaignsContractClient } from "@/common/contracts/core"; -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; -import { ViewerSessionProvider } from "@/common/viewer"; -import { CampaignBanner } from "@/entities/campaign/components/CampaignBanner"; -import { CampaignsList } from "@/entities/campaign/components/CampaignsList"; -import { FeaturedCampaigns } from "@/entities/campaign/components/FeaturedCampaigns"; +import { + Button, + Carousel, + CarouselApi, + CarouselContent, + PageWithBanner, +} from "@/common/ui/components"; +import { CampaignCarouselItem, CampaignsList } from "@/entities/campaign"; + +export const FeaturedCampaigns = ({ data }: { data: Campaign[] }) => { + const [api, setApi] = useState(); + const [current, setCurrent] = useState(0); + + useEffect(() => { + if (!api) return; + + setCurrent(api.selectedScrollSnap()); + + api.on("select", () => { + setCurrent(api.selectedScrollSnap()); + }); + }, [api]); + + if (!data?.length) { + return <>; + } + + return ( +
+
+
+

Featured Campaigns

+

{current + 1}/3

+
+
+ api?.scrollTo(current - 1)} + className="h-6 w-6 cursor-pointer rounded-full border border-gray-400 text-[14px] text-gray-500" + /> + api?.scrollTo(current + 1)} + className="h-6 w-6 cursor-pointer rounded-full border border-gray-400 text-[14px] text-gray-500" + /> +
+
+ + + {data?.length && + data?.slice(0, 3)?.map((data) => )} + + +
+ ); +}; export default function CampaignsPage() { const [campaigns, setCampaigns] = useState([]); @@ -23,13 +78,29 @@ export default function CampaignsPage() { return ( - +
+

Fund Your Ideas

+

+ { + "Bring your vision to life with a powerful fundraising campaign to support groundbreaking projects. Reach your goals and make a positive impact on your community" + } + + + {"Learn more"} + +

+ + +
+
); } - -CampaignsPage.getLayout = function getLayout(page: React.ReactNode) { - return {page}; -}; diff --git a/src/pages/deploy.tsx b/src/pages/deploy.tsx index b447a845..47fc6886 100644 --- a/src/pages/deploy.tsx +++ b/src/pages/deploy.tsx @@ -1,7 +1,6 @@ -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; +import { PageWithBanner } from "@/common/ui/components"; import InfoIcon from "@/common/ui/svg/InfoIcon"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider } from "@/common/viewer"; import { PotConfigurationEditor, usePotDeploymentSuccessMiddleware, @@ -11,32 +10,30 @@ export default function PotDeployPage() { usePotDeploymentSuccessMiddleware(); return ( - - -
- - Deploy Pot - + +
+ + Deploy Pot + -

- Deploy a Quadratic Funding Round -

+

+ Deploy a Quadratic Funding Round +

- - + + - - Learn more about quadratic funding - - -
+ + Learn more about quadratic funding + + +
- -
-
+ + ); } diff --git a/src/pages/edit-project/[projectId].tsx b/src/pages/edit-project/[projectId].tsx index fd82dba4..212e6799 100644 --- a/src/pages/edit-project/[projectId].tsx +++ b/src/pages/edit-project/[projectId].tsx @@ -1,6 +1,6 @@ -import { PageWithBanner, SpinnerOverlay, SplashScreen } from "@/common/ui/components"; +import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider, useViewerSession } from "@/common/viewer"; +import { useViewerSession } from "@/common/viewer"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; @@ -40,7 +40,3 @@ export default function EditProjectPage() { ); } - -EditProjectPage.getLayout = function getLayout(page: React.ReactNode) { - return {page}; -}; diff --git a/src/pages/feed/[account]/[block]/index.tsx b/src/pages/feed/[account]/[block]/index.tsx index d50f76b7..eb2c65fb 100644 --- a/src/pages/feed/[account]/[block]/index.tsx +++ b/src/pages/feed/[account]/[block]/index.tsx @@ -7,8 +7,6 @@ import remarkGfm from "remark-gfm"; import { fetchSinglePost, fetchTimeByBlockHeight } from "@/common/api/near-social"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; -import { SplashScreen } from "@/common/ui/components"; -import { ViewerSessionProvider } from "@/common/viewer"; import { AccountProfilePicture } from "@/entities/_shared/account"; export default function FeedAccountBlockPostPage() { @@ -110,7 +108,3 @@ export default function FeedAccountBlockPostPage() {
); } - -FeedAccountBlockPostPage.getLayout = function getLayout(page: React.ReactNode) { - return {page}; -}; From 149969cf2058eb3f3e31d577f1169d9ca19e33cd Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Tue, 21 Jan 2025 16:55:18 +0000 Subject: [PATCH 28/84] wip --- src/common/viewer/components.tsx | 15 ++++----------- src/pages/feed/index.tsx | 5 ----- src/pages/list/create/index.tsx | 8 ++------ src/pages/list/duplicate/[id].tsx | 8 ++------ src/pages/list/edit/[id]/index.tsx | 8 ++------ src/pages/register.tsx | 8 ++------ 6 files changed, 12 insertions(+), 40 deletions(-) diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx index 19955f7a..b9370dda 100644 --- a/src/common/viewer/components.tsx +++ b/src/common/viewer/components.tsx @@ -1,7 +1,4 @@ -import { useMemo } from "react"; - -import { isClient } from "@wpdas/naxios"; - +import { IS_CLIENT } from "../constants"; import { WalletProvider } from "./internal/wallet-provider"; export type ViewerSessionProviderProps = { @@ -9,11 +6,7 @@ export type ViewerSessionProviderProps = { }; /** - * Required for session bindings to be available on the client. - * On the server, the ssrFallback is rendered instead. + * Required for wallet and session bindings to be available on the client. */ -export const ViewerSessionProvider: React.FC = ({ children }) => { - const isCsr = useMemo(isClient, []); - - return isCsr ? {children} : children; -}; +export const ViewerSessionProvider: React.FC = ({ children }) => + IS_CLIENT ? {children} : children; diff --git a/src/pages/feed/index.tsx b/src/pages/feed/index.tsx index 818fec68..587afe3a 100644 --- a/src/pages/feed/index.tsx +++ b/src/pages/feed/index.tsx @@ -6,7 +6,6 @@ import { indexer } from "@/common/api/indexer"; import { fetchGlobalFeeds } from "@/common/api/near-social"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider } from "@/common/viewer"; import { PostCard } from "@/entities/post"; export default function FeedPage() { @@ -109,7 +108,3 @@ export default function FeedPage() {
); } - -FeedPage.getLayout = function getLayout(page: React.ReactNode) { - return {page}; -}; diff --git a/src/pages/list/create/index.tsx b/src/pages/list/create/index.tsx index a6e18671..4a328011 100644 --- a/src/pages/list/create/index.tsx +++ b/src/pages/list/create/index.tsx @@ -1,15 +1,11 @@ -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; -import { ViewerSessionProvider } from "@/common/viewer"; +import { PageWithBanner } from "@/common/ui/components"; import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function Page() { return ( - - - - + ); } diff --git a/src/pages/list/duplicate/[id].tsx b/src/pages/list/duplicate/[id].tsx index 3a5f1e59..39d87ec2 100644 --- a/src/pages/list/duplicate/[id].tsx +++ b/src/pages/list/duplicate/[id].tsx @@ -1,15 +1,11 @@ -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; -import { ViewerSessionProvider } from "@/common/viewer"; +import { PageWithBanner } from "@/common/ui/components"; import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function DuplicateList() { return ( - - - - + ); } diff --git a/src/pages/list/edit/[id]/index.tsx b/src/pages/list/edit/[id]/index.tsx index d3727198..1f7c9f77 100644 --- a/src/pages/list/edit/[id]/index.tsx +++ b/src/pages/list/edit/[id]/index.tsx @@ -1,17 +1,13 @@ import React from "react"; -import { PageWithBanner, SplashScreen } from "@/common/ui/components"; -import { ViewerSessionProvider } from "@/common/viewer"; +import { PageWithBanner } from "@/common/ui/components"; import { CreateListHero, ListFormDetails } from "@/entities/list"; export default function Page() { return ( - - - - + ); } diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 02c4e06b..0b76dec1 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,6 +1,6 @@ -import { PageWithBanner, SpinnerOverlay, SplashScreen } from "@/common/ui/components"; +import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider, useViewerSession } from "@/common/viewer"; +import { useViewerSession } from "@/common/viewer"; import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; import { useGlobalStoreSelector } from "@/store"; @@ -40,7 +40,3 @@ export default function RegisterPage() { ); } - -RegisterPage.getLayout = function getLayout(page: React.ReactNode) { - return {page}; -}; From 4fc4a767ea036d7c6e9279c14cc4e0a94b14cdfa Mon Sep 17 00:00:00 2001 From: Jiku Godwill Nsanwi Date: Tue, 21 Jan 2025 18:15:01 +0100 Subject: [PATCH 29/84] added project isEdit check --- .../profile-setup/components/ProjectEditor.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/ProjectEditor.tsx index d3d581b7..30e63593 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/ProjectEditor.tsx @@ -3,6 +3,8 @@ import { FC, useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; +import { indexer } from "@/common/api/indexer"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { Button, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { @@ -43,7 +45,7 @@ interface ProjectEditorProps { export const ProjectEditor: FC = ({ accountId }) => { const router = useRouter(); const isNewAccount = accountId === undefined; - // // const [editContractIndex, setEditContractIndex] = useState(); + // const [editContractIndex, setEditContractIndex] = useState(); // const [initialNameSet, setInitialNameSet] = useState(false); // Local state for modals @@ -84,6 +86,16 @@ export const ProjectEditor: FC = ({ accountId }) => { [avatarSrc, backgroundSrc, socialDbSnapshot], ); + const { data: listRegistrations } = indexer.useListRegistrations({ listId: 1 }); + + const hasRegistrationSubmitted = useMemo( + () => + listRegistrations?.results.find( + (registration) => registration.registrant.id === PUBLIC_GOODS_REGISTRY_LIST_ID.toString(), + ) !== undefined, + [listRegistrations?.results], + ); + const { form, values, @@ -97,7 +109,7 @@ export const ProjectEditor: FC = ({ accountId }) => { } = useProjectEditorForm({ defaultValues, onSuccess: () => router.push(rootPathnames.PROJECTS_LIST), - isEdit: !!socialDbSnapshot, + isEdit: hasRegistrationSubmitted, }); useEffect(() => { From 7f0d02a1dc5a0e6eda40aa8c93163272a8bd3160 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Wed, 22 Jan 2025 11:24:15 +0000 Subject: [PATCH 30/84] wip --- src/common/api/indexer/hooks.ts | 14 ++- .../api/indexer/internal/client.generated.ts | 2 + .../components/_deprecated}/InfoSegment.tsx | 0 .../pot/components/PotDonationEntry.tsx | 2 +- .../pot/components/PotDonationStats.tsx | 2 +- .../{ProjectEditor.tsx => form.tsx} | 108 ++++-------------- src/features/profile-setup/hooks/forms.ts | 81 +++++++------ .../hooks/useInitProjectState.ts | 2 +- src/features/profile-setup/index.ts | 2 +- src/features/profile-setup/models/effects.ts | 18 ++- src/features/profile-setup/models/index.ts | 6 +- src/features/profile-setup/models/schemas.ts | 2 +- src/features/profile-setup/models/types.ts | 4 +- .../components/ProfileLayoutControls.tsx | 6 +- src/pages/edit-project/[projectId].tsx | 42 ------- src/pages/index.tsx | 6 +- src/pages/profile/edit.tsx | 68 +++++++++++ src/pages/register.tsx | 56 ++++++--- src/pathnames.ts | 8 +- 19 files changed, 218 insertions(+), 211 deletions(-) rename src/{features/profile-setup/components => common/ui/components/_deprecated}/InfoSegment.tsx (100%) rename src/features/profile-setup/components/{ProjectEditor.tsx => form.tsx} (78%) delete mode 100644 src/pages/edit-project/[projectId].tsx create mode 100644 src/pages/profile/edit.tsx diff --git a/src/common/api/indexer/hooks.ts b/src/common/api/indexer/hooks.ts index e34924a1..6783e4b4 100644 --- a/src/common/api/indexer/hooks.ts +++ b/src/common/api/indexer/hooks.ts @@ -70,14 +70,16 @@ export const useAccountActivePots = ({ * https://test-dev.potlock.io/api/schema/swagger-ui/#/v1/v1_accounts_list_registrations_retrieve */ export const useAccountListRegistrations = ({ + enabled = true, accountId, ...params -}: Partial & generatedClient.V1AccountsListRegistrationsRetrieveParams) => { - const queryResult = generatedClient.useV1AccountsListRegistrationsRetrieve( - accountId ?? "noop", - params, - { ...INDEXER_CLIENT_CONFIG, swr: { enabled: Boolean(accountId) } }, - ); +}: ByAccountId & + generatedClient.V1AccountsListRegistrationsRetrieveParams & + ConditionalActivation) => { + const queryResult = generatedClient.useV1AccountsListRegistrationsRetrieve(accountId, params, { + ...INDEXER_CLIENT_CONFIG, + swr: { enabled }, + }); return { ...queryResult, data: queryResult.data?.data }; }; diff --git a/src/common/api/indexer/internal/client.generated.ts b/src/common/api/indexer/internal/client.generated.ts index b8b7c13b..e38376c1 100644 --- a/src/common/api/indexer/internal/client.generated.ts +++ b/src/common/api/indexer/internal/client.generated.ts @@ -1128,6 +1128,8 @@ export interface ListRegistration { admin_notes?: string | null; /** Registration id. */ readonly id: number; + /** List registered. */ + readonly list_id: number; registered_by: Account; registrant: Account; /** diff --git a/src/features/profile-setup/components/InfoSegment.tsx b/src/common/ui/components/_deprecated/InfoSegment.tsx similarity index 100% rename from src/features/profile-setup/components/InfoSegment.tsx rename to src/common/ui/components/_deprecated/InfoSegment.tsx diff --git a/src/entities/pot/components/PotDonationEntry.tsx b/src/entities/pot/components/PotDonationEntry.tsx index a774b918..36bf9d31 100644 --- a/src/entities/pot/components/PotDonationEntry.tsx +++ b/src/entities/pot/components/PotDonationEntry.tsx @@ -41,7 +41,7 @@ export const PotDonationEntry = ({ ? `${routesPath.PROFILE}/${donorId}` : `${routesPath.PROFILE}/${projectId || recipientId}`; - const recipientUrl = `${routesPath.PROJECT}/${recipientId}`; + const recipientUrl = `${routesPath.PROFILE}/${recipientId}`; const name = truncate(donorId, 15); const recipientName = truncate(recipientId, 15); diff --git a/src/entities/pot/components/PotDonationStats.tsx b/src/entities/pot/components/PotDonationStats.tsx index 37de7e65..1fe4d071 100644 --- a/src/entities/pot/components/PotDonationStats.tsx +++ b/src/entities/pot/components/PotDonationStats.tsx @@ -84,7 +84,7 @@ const Donation = ({ donorId, nearAmount, index, usdToggle }: DonationProps) => { ? (nativeToken?.usdPrice?.mul(nearAmount).toFixed(2) ?? 0) : nearAmount.toFixed(2); - const url = `${rootPathnames.PROJECT}/${donorId}`; + const url = `${rootPathnames.PROFILE}/${donorId}`; return ( diff --git a/src/features/profile-setup/components/ProjectEditor.tsx b/src/features/profile-setup/components/form.tsx similarity index 78% rename from src/features/profile-setup/components/ProjectEditor.tsx rename to src/features/profile-setup/components/form.tsx index 30e63593..2e5b2455 100644 --- a/src/features/profile-setup/components/ProjectEditor.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -1,24 +1,22 @@ -import { FC, useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; -import { indexer } from "@/common/api/indexer"; -import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import type { ByAccountId } from "@/common/types"; import { Button, FormField } from "@/common/ui/components"; +import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; +import { useToast } from "@/common/ui/hooks"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, useAccountSocialProfile, } from "@/entities/_shared/account"; -import { useSession } from "@/entities/_shared/session"; import { rootPathnames } from "@/pathnames"; import AddFundingSourceModal from "./AddFundingSourceModal"; import AddTeamMembersModal from "./AddTeamMembersModal"; -import DAOInProgress from "./DAOInProgress"; import EditSmartContractModal from "./EditSmartContractModal"; -import { ErrorModal } from "./ErrorModal"; import { AccountStack, CustomInput, @@ -27,7 +25,6 @@ import { Row, } from "./form-elements"; import FundingSourceTable from "./FundingSourceTable"; -import InfoSegment from "./InfoSegment"; import Profile from "./Profile"; import Repositories from "./Repositories"; import { SmartContracts } from "./SmartContracts"; @@ -35,37 +32,27 @@ import SocialLinks from "./SocialLinks"; import { LowerBannerContainer, LowerBannerContainerLeft } from "./styles"; import SubHeader from "./SubHeader"; import SuccessfulRegister from "./SuccessfulRegister"; -import { useProjectEditorForm } from "../hooks/forms"; -import { ProjectEditorInputs } from "../models/types"; +import { type ProfileSetupFormParams, useProfileSetupForm } from "../hooks/forms"; +import { ProfileSetupInputs } from "../models/types"; -interface ProjectEditorProps { - accountId: string; -} +export type ProfileSetupFormProps = Pick & {}; -export const ProjectEditor: FC = ({ accountId }) => { +export const ProfileSetupForm: React.FC = ({ accountId, mode }) => { const router = useRouter(); - const isNewAccount = accountId === undefined; - // const [editContractIndex, setEditContractIndex] = useState(); - // const [initialNameSet, setInitialNameSet] = useState(false); + const { toast } = useToast(); - // Local state for modals const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); const [addFundingModalOpen, setAddFundingModalOpen] = useState(false); const [editFundingIndex, setEditFundingIndex] = useState(); const [editContractIndex, setEditContractIndex] = useState(); - const sessionData = useSession(); - const { profile: socialDbSnapshot, avatarSrc, backgroundSrc, - } = useAccountSocialProfile({ - accountId: accountId ?? "noop", - enabled: !isNewAccount, - }); + } = useAccountSocialProfile({ accountId }); - const defaultValues = useMemo>( + const defaultValues = useMemo>( () => socialDbSnapshot === undefined ? { profileImage: ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } @@ -74,26 +61,20 @@ export const ProjectEditor: FC = ({ accountId }) => { description: socialDbSnapshot.description, publicGoodReason: socialDbSnapshot.plPublicGoodReason, teamMembers: socialDbSnapshot.plTeam ? JSON.parse(socialDbSnapshot.plTeam) : undefined, + categories: socialDbSnapshot.plCategories ? JSON.parse(socialDbSnapshot.plCategories) : undefined, + github: socialDbSnapshot.plGithubRepos ? JSON.parse(socialDbSnapshot.plGithubRepos) : undefined, + backgroundImage: backgroundSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, profileImage: avatarSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, }, - [avatarSrc, backgroundSrc, socialDbSnapshot], - ); - const { data: listRegistrations } = indexer.useListRegistrations({ listId: 1 }); - - const hasRegistrationSubmitted = useMemo( - () => - listRegistrations?.results.find( - (registration) => registration.registrant.id === PUBLIC_GOODS_REGISTRY_LIST_ID.toString(), - ) !== undefined, - [listRegistrations?.results], + [avatarSrc, backgroundSrc, socialDbSnapshot], ); const { @@ -106,29 +87,21 @@ export const ProjectEditor: FC = ({ accountId }) => { updateRepositories, addRepository, errors, - } = useProjectEditorForm({ + } = useProfileSetupForm({ + accountId, defaultValues, onSuccess: () => router.push(rootPathnames.PROJECTS_LIST), - isEdit: hasRegistrationSubmitted, + mode, }); - useEffect(() => { - form.reset(defaultValues, { - keepDefaultValues: true, - keepDirty: false, - }); - }, [defaultValues]); - useEffect(() => { // Set initial focus to name input. form.setFocus("name"); - }, []); + }, [form]); const stringifiedDefaultValues = JSON.stringify(defaultValues); const stringifiedValues = JSON.stringify(values); - const isOwner = values.isDao ? accountId === values.daoAddress : accountId === accountId; - const categoryChangeHandler = useCallback( (categories: string[]) => updateCategories(categories), [updateCategories], @@ -151,13 +124,10 @@ export const ProjectEditor: FC = ({ accountId }) => { }, [stringifiedValues, stringifiedDefaultValues]); // const resetUrl = useCallback(() => { - // router.push(rootPathnames.CREATE_PROJECT); + // router.push(rootPathnames.REGISTER); // resetForm(); // }, [router, resetForm]); - // Add loading state - const [isDataLoading, setIsDataLoading] = useState(true); - // Prevent form render while loading if (form.formState.isLoading) { return ; @@ -173,34 +143,6 @@ export const ProjectEditor: FC = ({ accountId }) => { const projectEditorText = getProjectEditorText(); - const isRepositoriesValid = values.githubRepositories && values.githubRepositories.length > 0; - - // Wait for wallet - if (sessionData.isMetadataLoading) { - return ; - } - - // if (isAuthenticated && values.checkPreviousProjectDataStatus !== "ready") { - // return ; - // } - - // must be signed in - if (!accountId) { - return ; - } - - // If it is Edit & not the owner - if (!isOwner && socialDbSnapshot) { - return ( - - ); - } - - const isEdit = !!socialDbSnapshot; - // // DAO Status - In Progress // if ( // values.isDao && @@ -210,12 +152,12 @@ export const ProjectEditor: FC = ({ accountId }) => { // return ; // } - if (form.formState.isSubmitted && location.pathname === rootPathnames.CREATE_PROJECT) { + if (form.formState.isSubmitted && location.pathname === rootPathnames.REGISTER) { return (
); @@ -278,12 +220,6 @@ export const ProjectEditor: FC = ({ accountId }) => { }} /> - {/* */} - ; - onSuccess?: () => void; - isEdit: boolean; -}) => { +export type ProfileSetupFormParams = ByAccountId & + Pick & { + onSuccess?: () => void; + defaultValues?: Partial; + }; + +export const useProfileSetupForm = ({ + accountId, + mode, + onSuccess, + defaultValues, +}: ProfileSetupFormParams) => { const [submitting, setSubmitting] = useState(false); - const [crossFieldErrors, setCrossFieldErrors] = useState>({}); + const [crossFieldErrors, setCrossFieldErrors] = useState>({}); const { isSignedIn } = useSession(); - const form = useForm({ - resolver: zodResolver(projectEditorSchema), + const form = useForm({ + resolver: zodResolver(profileSetupSchema), mode: "onChange", - defaultValues: {}, + defaultValues, + + resetOptions: { + keepDefaultValues: true, + keepDirty: false, + }, }); const values = useWatch({ control: form.control }); // Cross-field validation useEffect(() => { - void projectEditorSchema + void profileSetupSchema .parseAsync(values) .then(() => setCrossFieldErrors({})) .catch((error: ZodError) => @@ -74,28 +86,24 @@ export const useProjectEditorForm = (options: { form.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true }); }, [form, values.githubRepositories]); - const onSubmit: SubmitHandler = useCallback( - async (_) => { - // not using form data, using store data provided by form - if (isSignedIn) { - setSubmitting(true); - - saveProject({ isEdit: options.isEdit }) - .then(async (result) => { - if (result.success) { - console.log("Opening wallet for approval..."); - } else { - dispatch.projectEditor.submissionStatus("pending"); - console.log("error while saving"); - } - }) - .catch((error) => { - console.error(error); - }); - } - }, - [isSignedIn, options.isEdit], - ); + const onSubmit: SubmitHandler = useCallback(async () => { + if (isSignedIn) { + setSubmitting(true); + + save({ mode }) + .then(async (result) => { + if (result.success) { + console.log("Opening wallet for approval..."); + } else { + dispatch.projectEditor.submissionStatus("pending"); + console.log("error while saving"); + } + }) + .catch((error) => { + console.error(error); + }); + } + }, [isSignedIn, mode]); return { form: { @@ -105,6 +113,7 @@ export const useProjectEditorForm = (options: { errors: { ...form.formState.errors, ...crossFieldErrors }, }, }, + errors: form.formState.errors, values, isSubmitting: submitting, diff --git a/src/features/profile-setup/hooks/useInitProjectState.ts b/src/features/profile-setup/hooks/useInitProjectState.ts index a1e122d2..824b3cf2 100644 --- a/src/features/profile-setup/hooks/useInitProjectState.ts +++ b/src/features/profile-setup/hooks/useInitProjectState.ts @@ -90,7 +90,7 @@ export const useInitProjectState = () => { dispatch.projectEditor.isRegistered(!!register); // Auto set the project to DONE status if it's already registered & this is create project page - if (register && location.pathname.includes(routesPath.CREATE_PROJECT)) { + if (register && location.pathname.includes(routesPath.REGISTER)) { dispatch.projectEditor.submissionStatus("done"); dispatch.projectEditor.setSubmissionError(""); } diff --git a/src/features/profile-setup/index.ts b/src/features/profile-setup/index.ts index ab1714d6..ac1df85d 100644 --- a/src/features/profile-setup/index.ts +++ b/src/features/profile-setup/index.ts @@ -1,3 +1,3 @@ -export * from "./components/ProjectEditor"; +export * from "./components/form"; export * from "./hooks/useInitProjectState"; export { projectEditorModel, projectEditorModelKey } from "./models"; diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index 3a0de96c..6306ddbe 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -27,7 +27,11 @@ const getSocialData = async (accountId: string) => { } }; -export const saveProject = async ({ isEdit }: { isEdit: boolean }) => { +export type ProfileSaveInputs = { + mode: "register" | "update"; +}; + +export const save = async ({ mode }: ProfileSaveInputs) => { const data = store.getState().projectEditor; const accountId = data.isDao ? data.daoAddress : data.accountId; @@ -85,7 +89,7 @@ export const saveProject = async ({ isEdit }: { isEdit: boolean }) => { let daoTransactions: Transaction[] = []; // if this is a creation action, we need to add the registry - if (!isEdit) { + if (mode === "register") { transactions.push( // lists.potlock.near buildTransaction("register_batch", { @@ -110,11 +114,14 @@ export const saveProject = async ({ isEdit }: { isEdit: boolean }) => { return { receiverId: data.daoAddress, method: "add_proposal", + args: { proposal: { - description: data.isEdit - ? "Update project on POTLOCK (via NEAR Social)" - : "Create project on POTLOCK (2 steps: Register information on NEAR Social and register on POTLOCK)", + description: + mode === "register" + ? "Create project on POTLOCK (2 steps: Register information on NEAR Social and register on POTLOCK)" + : "Update project on POTLOCK (via NEAR Social)", + kind: { FunctionCall: { receiver_id: tx.receiverId, @@ -123,6 +130,7 @@ export const saveProject = async ({ isEdit }: { isEdit: boolean }) => { }, }, }, + deposit: daoPolicy?.proposal_bond || MIN_PROPOSAL_DEPOSIT_FALLBACK, gas: FULL_TGAS, } as Transaction; diff --git a/src/features/profile-setup/models/index.ts b/src/features/profile-setup/models/index.ts index 3dcc76a3..d11a97b1 100644 --- a/src/features/profile-setup/models/index.ts +++ b/src/features/profile-setup/models/index.ts @@ -10,7 +10,7 @@ import { rootPathnames } from "@/pathnames"; import { useGlobalStoreSelector } from "@/store"; import { AppModel } from "@/store/models"; -import { AddFundingSourceInputs, ProjectEditorInputs } from "./types"; +import { AddFundingSourceInputs, ProfileSetupInputs } from "./types"; export type SocialImagesInputs = ByAccountId & { socialData?: NEARSocialUserProfile | null; @@ -63,7 +63,7 @@ type ExtraTypes = { daoProjectProposal: Proposal | null; isRepositoryRequired: boolean; }; -export type ProjectEditorState = ProjectEditorInputs & ExtraTypes; +export type ProjectEditorState = ProfileSetupInputs & ExtraTypes; /** * Create Project State @@ -307,7 +307,7 @@ export const projectEditorModel = createModel()({ const data: Partial = {}; // Set the isEdit status - data.isEdit = location.pathname.includes(rootPathnames.EDIT_PROJECT); + data.isEdit = location.pathname.includes(rootPathnames.EDIT_PROFILE); // Get profile data & profile images const projectProfileData = await fetchSocialImages({ diff --git a/src/features/profile-setup/models/schemas.ts b/src/features/profile-setup/models/schemas.ts index ce3cbf06..a6660ab7 100644 --- a/src/features/profile-setup/models/schemas.ts +++ b/src/features/profile-setup/models/schemas.ts @@ -24,7 +24,7 @@ export const addFundingSourceSchema = object({ .max(50, "Must be less than 50 characters"), }); -export const projectEditorSchema = object({ +export const profileSetupSchema = object({ name: string() .min(3, "Must be at least 3 characters long") .max(100, "Must be less than 100 characters long"), diff --git a/src/features/profile-setup/models/types.ts b/src/features/profile-setup/models/types.ts index 65660a1f..65c21f77 100644 --- a/src/features/profile-setup/models/types.ts +++ b/src/features/profile-setup/models/types.ts @@ -1,7 +1,7 @@ import { z } from "zod"; -import { addFundingSourceSchema, projectEditorSchema } from "./schemas"; +import { addFundingSourceSchema, profileSetupSchema } from "./schemas"; -export type ProjectEditorInputs = z.infer; +export type ProfileSetupInputs = z.infer; export type AddFundingSourceInputs = z.infer; diff --git a/src/layout/profile/components/ProfileLayoutControls.tsx b/src/layout/profile/components/ProfileLayoutControls.tsx index 2978ce8b..41761ab6 100644 --- a/src/layout/profile/components/ProfileLayoutControls.tsx +++ b/src/layout/profile/components/ProfileLayoutControls.tsx @@ -15,9 +15,9 @@ import { AccountProfileTags, useAccountSocialProfile, } from "@/entities/_shared/account"; -import { useSession, useWallet } from "@/entities/_shared/session"; +import { useSession } from "@/entities/_shared/session"; import { useDonation } from "@/features/donation"; -import routesPath, { rootPathnames } from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; type Props = { accountId: string; @@ -171,7 +171,7 @@ export const ProfileLayoutControls = ({ accountId, isProject }: Props) => {
{isOwner && (
- + diff --git a/src/pages/edit-project/[projectId].tsx b/src/pages/edit-project/[projectId].tsx deleted file mode 100644 index a4160c71..00000000 --- a/src/pages/edit-project/[projectId].tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; -import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; -import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; - -export default function EditProjectPage() { - const { isSignedIn, accountId } = useSession(); - useInitProjectState(); - - return ( - - {!isSignedIn ? ( -
-

You need to be athenticated blah blah blah

- -
- ) : ( - <> -
-

- {"Edit Project"} -

- -

- { - "Create a profile for your project to receive donations and qualify for funding rounds." - } -

-
- - {!isSignedIn && } - {accountId && } - - )} -
- ); -} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 8f8bad40..6c081be3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -9,7 +9,7 @@ import { cn } from "@/common/ui/utils"; import { useSessionReduxStore } from "@/entities/_shared/session"; import { ProjectCard, ProjectDiscovery } from "@/entities/project"; import { DonateRandomly } from "@/features/donation"; -import routesPath from "@/pathnames"; +import { rootPathnames } from "@/pathnames"; import { useGlobalStoreSelector } from "@/store"; export const FEATURED_PROJECT_ACCOUNT_IDS = @@ -78,8 +78,8 @@ const WelcomeBanner = () => { diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx new file mode 100644 index 00000000..a5c4d513 --- /dev/null +++ b/src/pages/profile/edit.tsx @@ -0,0 +1,68 @@ +import { useEffect, useMemo } from "react"; + +import { useRouter } from "next/router"; + +import { indexer } from "@/common/api/indexer"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { PageWithBanner } from "@/common/ui/components"; +import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; +import { cn } from "@/common/ui/utils"; +import { useSession } from "@/entities/_shared/session"; +import { ProfileSetupForm } from "@/features/profile-setup"; +import { rootPathnames } from "@/pathnames"; + +export default function EditProjectPage() { + const router = useRouter(); + const viewer = useSession(); + + const { data: listRegistrations } = indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); + + const hasRegistrationSubmitted = useMemo( + () => + listRegistrations?.results.find( + ({ list_id }) => list_id === PUBLIC_GOODS_REGISTRY_LIST_ID, + ) !== undefined, + + [listRegistrations?.results], + ); + + useEffect(() => { + if (hasRegistrationSubmitted) { + router.push(rootPathnames.REGISTER); + } + }, [hasRegistrationSubmitted, router]); + + return ( + +
+

+ {"Edit Project"} +

+ +

+ {"Create a profile for your project to receive donations and qualify for funding rounds."} +

+
+ + {viewer.isMetadataLoading ? ( + + ) : ( + <> + {viewer.isSignedIn ? ( + + ) : ( + + )} + + )} +
+ ); +} diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 4bcf5c8d..516d9fca 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,22 +1,39 @@ -import { PageWithBanner, SpinnerOverlay } from "@/common/ui/components"; +import { useEffect, useMemo } from "react"; + +import { useRouter } from "next/router"; + +import { indexer } from "@/common/api/indexer"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { PageWithBanner } from "@/common/ui/components"; +import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { cn } from "@/common/ui/utils"; import { useSession } from "@/entities/_shared/session"; -import { ProjectEditor, useInitProjectState } from "@/features/profile-setup"; -import { useGlobalStoreSelector } from "@/store"; +import { ProfileSetupForm } from "@/features/profile-setup"; +import { rootPathnames } from "@/pathnames"; export default function RegisterPage() { - const { accountId = "", isSignedIn } = useSession(); - useInitProjectState(); + const router = useRouter(); + const viewer = useSession(); + + const { data: listRegistrations } = indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); - // state used to show spinner during the data post - const { submissionStatus, checkRegistrationStatus, checkPreviousProjectDataStatus } = - useGlobalStoreSelector((state) => state.projectEditor); + const hasRegistrationSubmitted = useMemo( + () => + listRegistrations?.results.find( + ({ list_id }) => list_id === PUBLIC_GOODS_REGISTRY_LIST_ID, + ) !== undefined, + + [listRegistrations?.results], + ); - const showSpinner = isSignedIn - ? submissionStatus === "sending" || - checkRegistrationStatus !== "ready" || - checkPreviousProjectDataStatus !== "ready" - : false; + useEffect(() => { + if (hasRegistrationSubmitted) { + router.push(rootPathnames.EDIT_PROFILE); + } + }, [hasRegistrationSubmitted, router]); return ( @@ -35,8 +52,17 @@ export default function RegisterPage() { - {showSpinner && } - + {viewer.isMetadataLoading ? ( + + ) : ( + <> + {viewer.isSignedIn ? ( + + ) : ( + + )} + + )} ); } diff --git a/src/pathnames.ts b/src/pathnames.ts index 82eccf05..d7710baa 100644 --- a/src/pathnames.ts +++ b/src/pathnames.ts @@ -1,9 +1,9 @@ export const rootPathnames = { CURRENT: "", - CREATE_PROJECT: "/register", - EDIT_PROJECT: "/edit-project", PROJECTS_LIST: "/", - PROJECT: "/profile", + REGISTER: "/register", + PROFILE: "/profile", + EDIT_PROFILE: "/profile/edit", CART: "/cart", FEED: "/feed", POTS: "/pots", @@ -11,9 +11,7 @@ export const rootPathnames = { DEPLOY_POT: "/deploy", DONORS: "/donors", LIST: "/lists", - PROFILE: "/profile", CAMPAIGNS: "/campaigns", - EDIT_PROFILE: "/editprofile", }; // ?INFO: This default export is for temporary backwards compatibility From e90a2ae1d4fa5264c43e600fee7ccddfe25c908d Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Wed, 22 Jan 2025 13:00:57 +0000 Subject: [PATCH 31/84] wip --- src/entities/list/components/AccountCard.tsx | 2 +- .../components/SuccessfulRegister.tsx | 42 ------------------- .../profile-setup/components/form.tsx | 13 ------ src/features/profile-setup/models/effects.ts | 3 +- src/features/profile-setup/types.ts | 1 + .../components/ProfileLayoutControls.tsx | 4 +- src/pages/profile/edit.tsx | 32 ++++++++------ src/pages/register.tsx | 35 ++++++++++------ 8 files changed, 48 insertions(+), 84 deletions(-) delete mode 100644 src/features/profile-setup/components/SuccessfulRegister.tsx create mode 100644 src/features/profile-setup/types.ts diff --git a/src/entities/list/components/AccountCard.tsx b/src/entities/list/components/AccountCard.tsx index 334d91b2..ab6f507d 100644 --- a/src/entities/list/components/AccountCard.tsx +++ b/src/entities/list/components/AccountCard.tsx @@ -136,7 +136,7 @@ export const AccountCard = ({ diff --git a/src/features/profile-setup/components/SuccessfulRegister.tsx b/src/features/profile-setup/components/SuccessfulRegister.tsx deleted file mode 100644 index 0d9fe20d..00000000 --- a/src/features/profile-setup/components/SuccessfulRegister.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import Link from "next/link"; - -import { Button } from "@/common/ui/components"; -import routesPath from "@/pathnames"; -import { dispatch } from "@/store"; - -const SuccessfulRegister = ({ - registeredProject, - isEdit, -}: { - registeredProject: string; - isEdit?: boolean; -}) => { - const refreshStatus = () => { - // Reset - dispatch.projectEditor.submissionStatus("pending"); - dispatch.projectEditor.setBackgroundImage(""); - dispatch.projectEditor.setProfileImage(""); - }; - - return ( -
- {isEdit ? ( -

You've edited your project successfully!

- ) : ( -

You've successfully registered!

- )} -
- - - - - - -
-
- ); -}; - -export default SuccessfulRegister; diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index 2e5b2455..6af25241 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -31,7 +31,6 @@ import { SmartContracts } from "./SmartContracts"; import SocialLinks from "./SocialLinks"; import { LowerBannerContainer, LowerBannerContainerLeft } from "./styles"; import SubHeader from "./SubHeader"; -import SuccessfulRegister from "./SuccessfulRegister"; import { type ProfileSetupFormParams, useProfileSetupForm } from "../hooks/forms"; import { ProfileSetupInputs } from "../models/types"; @@ -152,17 +151,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m // return ; // } - if (form.formState.isSubmitted && location.pathname === rootPathnames.REGISTER) { - return ( -
- -
- ); - } - console.log("Form values:", values); console.log(form.formState.errors, form.formState.isValid); @@ -176,7 +164,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m } return ( - // Container
diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index 6306ddbe..b45438fc 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -15,6 +15,7 @@ import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import deepObjectDiff from "@/common/lib/deepObjectDiff"; import { store } from "@/store"; +import type { ProfileSetupMode } from "../types"; import getSocialDataFormat from "../utils/getSocialDataFormat"; const getSocialData = async (accountId: string) => { @@ -28,7 +29,7 @@ const getSocialData = async (accountId: string) => { }; export type ProfileSaveInputs = { - mode: "register" | "update"; + mode: ProfileSetupMode; }; export const save = async ({ mode }: ProfileSaveInputs) => { diff --git a/src/features/profile-setup/types.ts b/src/features/profile-setup/types.ts new file mode 100644 index 00000000..7612f44e --- /dev/null +++ b/src/features/profile-setup/types.ts @@ -0,0 +1 @@ +export type ProfileSetupMode = "register" | "update"; diff --git a/src/layout/profile/components/ProfileLayoutControls.tsx b/src/layout/profile/components/ProfileLayoutControls.tsx index 41761ab6..8abf563e 100644 --- a/src/layout/profile/components/ProfileLayoutControls.tsx +++ b/src/layout/profile/components/ProfileLayoutControls.tsx @@ -171,9 +171,9 @@ export const ProfileLayoutControls = ({ accountId, isProject }: Props) => {
{isOwner && (
- +
diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx index a5c4d513..6df7b2fe 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/edit.tsx @@ -15,25 +15,33 @@ export default function EditProjectPage() { const router = useRouter(); const viewer = useSession(); - const { data: listRegistrations } = indexer.useAccountListRegistrations({ - enabled: viewer.isSignedIn, - accountId: viewer.accountId ?? "noop", - }); + const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = + indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); const hasRegistrationSubmitted = useMemo( () => - listRegistrations?.results.find( - ({ list_id }) => list_id === PUBLIC_GOODS_REGISTRY_LIST_ID, - ) !== undefined, + !isAccountListRegistrationDataLoading && + listRegistrations !== undefined && + listRegistrations.results.find(({ list_id }) => list_id === PUBLIC_GOODS_REGISTRY_LIST_ID) !== + undefined, - [listRegistrations?.results], + [isAccountListRegistrationDataLoading, listRegistrations], ); useEffect(() => { - if (hasRegistrationSubmitted) { + if (viewer.isSignedIn && !isAccountListRegistrationDataLoading && !hasRegistrationSubmitted) { router.push(rootPathnames.REGISTER); } - }, [hasRegistrationSubmitted, router]); + }, [ + hasRegistrationSubmitted, + isAccountListRegistrationDataLoading, + listRegistrations, + router, + viewer.isSignedIn, + ]); return ( @@ -52,8 +60,8 @@ export default function EditProjectPage() { - {viewer.isMetadataLoading ? ( - + {listRegistrations === undefined ? ( + ) : ( <> {viewer.isSignedIn ? ( diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 516d9fca..7c705e93 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -15,25 +15,34 @@ export default function RegisterPage() { const router = useRouter(); const viewer = useSession(); - const { data: listRegistrations } = indexer.useAccountListRegistrations({ - enabled: viewer.isSignedIn, - accountId: viewer.accountId ?? "noop", - }); + const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = + indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); const hasRegistrationSubmitted = useMemo( () => - listRegistrations?.results.find( - ({ list_id }) => list_id === PUBLIC_GOODS_REGISTRY_LIST_ID, - ) !== undefined, + !isAccountListRegistrationDataLoading && + listRegistrations !== undefined && + listRegistrations.results.find(({ list_id }) => list_id === PUBLIC_GOODS_REGISTRY_LIST_ID) !== + undefined, - [listRegistrations?.results], + [isAccountListRegistrationDataLoading, listRegistrations], ); useEffect(() => { - if (hasRegistrationSubmitted) { - router.push(rootPathnames.EDIT_PROFILE); + if (viewer.isSignedIn && !isAccountListRegistrationDataLoading && hasRegistrationSubmitted) { + router.push(`${rootPathnames.PROFILE}/${viewer.accountId}`); } - }, [hasRegistrationSubmitted, router]); + }, [ + hasRegistrationSubmitted, + isAccountListRegistrationDataLoading, + listRegistrations, + router, + viewer.accountId, + viewer.isSignedIn, + ]); return ( @@ -52,8 +61,8 @@ export default function RegisterPage() { - {viewer.isMetadataLoading ? ( - + {listRegistrations === undefined ? ( + ) : ( <> {viewer.isSignedIn ? ( From 9a5a5faa24f02a2384b404624cde002097c48fa0 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Wed, 22 Jan 2025 13:37:16 +0000 Subject: [PATCH 32/84] wip --- src/common/services/ipfs.ts | 12 ++++- .../campaign/components/CampaignForm.tsx | 4 +- .../list/components/ListFormDetails.tsx | 4 +- .../profile-setup/components/form.tsx | 32 ++++++------- .../{Profile.tsx => image-upload.tsx} | 47 +++++++++++-------- src/features/profile-setup/hooks/forms.ts | 20 +++++++- src/features/profile-setup/models/index.ts | 22 --------- src/features/profile-setup/models/schemas.ts | 2 +- 8 files changed, 76 insertions(+), 67 deletions(-) rename src/features/profile-setup/components/{Profile.tsx => image-upload.tsx} (76%) diff --git a/src/common/services/ipfs.ts b/src/common/services/ipfs.ts index 536eaed3..3cefbf46 100644 --- a/src/common/services/ipfs.ts +++ b/src/common/services/ipfs.ts @@ -1,8 +1,16 @@ -const uploadFileToIPFS = async (body: BodyInit) => +import { IPFS_NEAR_SOCIAL_URL } from "../constants"; + +export const nearSocialIpfsUpload = async (body: BodyInit) => fetch("https://ipfs.near.social/add", { method: "POST", headers: { Accept: "application/json" }, body, }); -export default uploadFileToIPFS; +export const nearSocialIpfsImageUpload = async (files: File[]) => + nearSocialIpfsUpload(files[0]).then((response) => { + if (response.ok) { + const data = response.json(); + return "cid" in data ? `${IPFS_NEAR_SOCIAL_URL}${data.cid}` : undefined; + } + }); diff --git a/src/entities/campaign/components/CampaignForm.tsx b/src/entities/campaign/components/CampaignForm.tsx index b815b4e9..8d7ea999 100644 --- a/src/entities/campaign/components/CampaignForm.tsx +++ b/src/entities/campaign/components/CampaignForm.tsx @@ -3,7 +3,7 @@ import { ChangeEvent, useEffect, useState } from "react"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; import { Campaign } from "@/common/contracts/core"; import { useRouteQuery, yoctoNearToFloat } from "@/common/lib"; -import uploadFileToIPFS from "@/common/services/ipfs"; +import { nearSocialIpfsUpload } from "@/common/services/ipfs"; import { Button, Form, FormField } from "@/common/ui/components"; import { NearInputField, TextAreaField, TextField } from "@/common/ui/form-fields"; @@ -62,7 +62,7 @@ export const CampaignForm = ({ existingData }: { existingData?: Campaign }) => { if (target.files && target.files[0]) { const reader = new FileReader(); setLoadingImageUpload(true); - const res = await uploadFileToIPFS(target.files[0]); + const res = await nearSocialIpfsUpload(target.files[0]); if (res.ok) { const data = await res.json(); diff --git a/src/entities/list/components/ListFormDetails.tsx b/src/entities/list/components/ListFormDetails.tsx index a14e31fa..c196caaa 100644 --- a/src/entities/list/components/ListFormDetails.tsx +++ b/src/entities/list/components/ListFormDetails.tsx @@ -10,7 +10,7 @@ import { indexer } from "@/common/api/indexer"; import { walletApi } from "@/common/api/near/client"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; import { RegistrationStatus, listsContractClient } from "@/common/contracts/core"; -import uploadFileToIPFS from "@/common/services/ipfs"; +import { nearSocialIpfsUpload } from "@/common/services/ipfs"; import { Button, Input } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { AccountGroup, AccountListItem, AccountProfilePicture } from "@/entities/_shared/account"; @@ -184,7 +184,7 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { if (target.files && target.files[0]) { const reader = new FileReader(); setLoadingImageUpload(true); - const res = await uploadFileToIPFS(target.files[0]); // Use the casted target + const res = await nearSocialIpfsUpload(target.files[0]); // Use the casted target if (res.ok) { const data = await res.json(); diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index 6af25241..eca688f8 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -3,7 +3,6 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; -import type { ByAccountId } from "@/common/types"; import { Button, FormField } from "@/common/ui/components"; import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; @@ -25,7 +24,7 @@ import { Row, } from "./form-elements"; import FundingSourceTable from "./FundingSourceTable"; -import Profile from "./Profile"; +import { ProfileSetupImageUpload } from "./image-upload"; import Repositories from "./Repositories"; import { SmartContracts } from "./SmartContracts"; import SocialLinks from "./SocialLinks"; @@ -81,9 +80,11 @@ export const ProfileSetupForm: React.FC = ({ accountId, m values, isSubmitting, onSubmit, - updateTeamMembers, updateCategories, + updateBackgroundImage, + updateProfileImage, updateRepositories, + updateTeamMembers, addRepository, errors, } = useProfileSetupForm({ @@ -101,12 +102,12 @@ export const ProfileSetupForm: React.FC = ({ accountId, m const stringifiedDefaultValues = JSON.stringify(defaultValues); const stringifiedValues = JSON.stringify(values); - const categoryChangeHandler = useCallback( + const onCategoriesChange = useCallback( (categories: string[]) => updateCategories(categories), [updateCategories], ); - const onMembersChangeHandler = useCallback( + const onTeamMembersChange = useCallback( (members: string[]) => updateTeamMembers(members), [updateTeamMembers], ); @@ -122,16 +123,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m return stringifiedDefaultValues !== stringifiedValues; }, [stringifiedValues, stringifiedDefaultValues]); - // const resetUrl = useCallback(() => { - // router.push(rootPathnames.REGISTER); - // resetForm(); - // }, [router, resetForm]); - - // Prevent form render while loading - if (form.formState.isLoading) { - return ; - } - const getProjectEditorText = () => { if (socialDbSnapshot) { return values.isDao ? "Add proposal to update project" : "Update your project"; @@ -167,7 +158,12 @@ export const ProfileSetupForm: React.FC = ({ accountId, m
- + @@ -187,7 +183,7 @@ export const ProfileSetupForm: React.FC = ({ accountId, m setAddTeamModalOpen(false)} - onMembersChange={onMembersChangeHandler} + onMembersChange={onTeamMembersChange} /> = ({ accountId, m /> diff --git a/src/features/profile-setup/components/Profile.tsx b/src/features/profile-setup/components/image-upload.tsx similarity index 76% rename from src/features/profile-setup/components/Profile.tsx rename to src/features/profile-setup/components/image-upload.tsx index 9c158dbf..ea0db5f2 100644 --- a/src/features/profile-setup/components/Profile.tsx +++ b/src/features/profile-setup/components/image-upload.tsx @@ -2,9 +2,11 @@ import { useState } from "react"; import Files from "react-files"; +import { nearSocialIpfsImageUpload } from "@/common/services/ipfs"; import { Button, Spinner } from "@/common/ui/components"; import CameraIcon from "@/common/ui/svg/CameraIcon"; -import { dispatch, useGlobalStoreSelector } from "@/store"; + +import type { ProfileSetupInputs } from "../models/types"; type Status = "ready" | "loading"; @@ -17,31 +19,40 @@ const useStatus = (initialStatus: Status = "ready") => { }; }; -const Profile = () => { - const { accountId, backgroundImage, profileImage } = useGlobalStoreSelector( - (state) => state.projectEditor, - ); +export type ProfileSetupImageUploadProps = Pick< + ProfileSetupInputs, + "backgroundImage" | "profileImage" +> & { + onBackgroundImageUploaded: (url: string) => void; + onProfileImageUploaded: (url: string) => void; +}; +export const ProfileSetupImageUpload: React.FC = ({ + backgroundImage, + profileImage, + onBackgroundImageUploaded, + onProfileImageUploaded, +}) => { const bgImageStatus = useStatus(); const profileImageStatus = useStatus(); - if (!accountId) { - return ""; - } - - const onBgImageChange = async (files: File[]) => { + const onBgImageChange = async (files?: File[]) => { if (files) { - bgImageStatus.setStatus("loading"); - await dispatch.projectEditor.uploadBackgroundImage(files); - bgImageStatus.setStatus("ready"); + nearSocialIpfsImageUpload(files).then((url) => { + if (url !== undefined) { + onBackgroundImageUploaded(url); + } + }); } }; - const onAvatarImageChange = async (files: File[]) => { + const onAvatarImageChange = async (files?: File[]) => { if (files) { - profileImageStatus.setStatus("loading"); - await dispatch.projectEditor.uploadProfileImage(files); - profileImageStatus.setStatus("ready"); + nearSocialIpfsImageUpload(files).then((url) => { + if (url !== undefined) { + onProfileImageUploaded(url); + } + }); } }; @@ -121,5 +132,3 @@ const Profile = () => {
); }; - -export default Profile; diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index e72ba296..dcf929dc 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -59,6 +59,22 @@ export const useProfileSetupForm = ({ ); }, [values]); + const updateBackgroundImage = useCallback( + (url: string) => { + form.setValue("backgroundImage", url, { shouldValidate: true }); + }, + + [form], + ); + + const updateProfileImage = useCallback( + (url: string) => { + form.setValue("profileImage", url, { shouldValidate: true }); + }, + + [form], + ); + // Form update handlers const updateTeamMembers = useCallback( (members: string[]) => { @@ -117,9 +133,11 @@ export const useProfileSetupForm = ({ errors: form.formState.errors, values, isSubmitting: submitting, - updateTeamMembers, + updateBackgroundImage, updateCategories, + updateProfileImage, updateRepositories, + updateTeamMembers, addRepository, onSubmit: form.handleSubmit(onSubmit), resetForm: form.reset, diff --git a/src/features/profile-setup/models/index.ts b/src/features/profile-setup/models/index.ts index d11a97b1..a596f63d 100644 --- a/src/features/profile-setup/models/index.ts +++ b/src/features/profile-setup/models/index.ts @@ -1,10 +1,8 @@ import { createModel } from "@rematch/core"; import { prop } from "remeda"; -import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; import { NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social"; import { getImage } from "@/common/services/images"; -import uploadFileToIPFS from "@/common/services/ipfs"; import { ByAccountId } from "@/common/types"; import { rootPathnames } from "@/pathnames"; import { useGlobalStoreSelector } from "@/store"; @@ -275,30 +273,10 @@ export const projectEditorModel = createModel()({ }, effects: (dispatch) => ({ - async uploadBackgroundImage(files: File[]) { - const res = await uploadFileToIPFS(files[0]); - - if (res.ok) { - const data = await res.json(); - dispatch.projectEditor.UPDATE_BACKGROUND_IMAGE(`${IPFS_NEAR_SOCIAL_URL}${data.cid}`); - return `${IPFS_NEAR_SOCIAL_URL}${data.cid}`; - } - }, - setBackgroundImage(backgroundUrl: string) { dispatch.projectEditor.UPDATE_BACKGROUND_IMAGE(backgroundUrl); }, - async uploadProfileImage(files: File[]) { - const res = await uploadFileToIPFS(files[0]); - - if (res.ok) { - const data = await res.json(); - dispatch.projectEditor.UPDATE_PROFILE_IMAGE(`${IPFS_NEAR_SOCIAL_URL}${data.cid}`); - return `${IPFS_NEAR_SOCIAL_URL}${data.cid}`; - } - }, - setProfileImage(profileImageUrl: string) { dispatch.projectEditor.UPDATE_PROFILE_IMAGE(profileImageUrl); }, diff --git a/src/features/profile-setup/models/schemas.ts b/src/features/profile-setup/models/schemas.ts index a6660ab7..27f9102b 100644 --- a/src/features/profile-setup/models/schemas.ts +++ b/src/features/profile-setup/models/schemas.ts @@ -31,7 +31,7 @@ export const profileSetupSchema = object({ isDao: boolean().default(false), daoAddress: string().optional(), - backgroundImage: string().min(3), + backgroundImage: string().min(3).optional(), profileImage: string().min(3), teamMembers: array(string()), categories: array(string()).min(1), From e715b2e965f753a652c059f10cce26b70a6aa1d0 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Wed, 22 Jan 2025 14:14:58 +0000 Subject: [PATCH 33/84] wip --- .../profile-setup/components/form.tsx | 38 +++----- src/features/profile-setup/hooks/forms.ts | 94 +++++++++++-------- src/features/profile-setup/models/effects.ts | 33 +++---- ...etSocialDataFormat.ts => normalization.ts} | 6 +- 4 files changed, 83 insertions(+), 88 deletions(-) rename src/features/profile-setup/utils/{getSocialDataFormat.ts => normalization.ts} (91%) diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index eca688f8..39059581 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -75,9 +75,19 @@ export const ProfileSetupForm: React.FC = ({ accountId, m [avatarSrc, backgroundSrc, socialDbSnapshot], ); + const onSuccess = useCallback(() => { + toast({ title: "Success!", description: "Project updated successfully" }); + }, [toast]); + + const onFailure = useCallback( + (errorMessage: string) => toast({ title: "Error", description: errorMessage }), + [toast], + ); + const { form, values, + isDisabled, isSubmitting, onSubmit, updateCategories, @@ -90,7 +100,8 @@ export const ProfileSetupForm: React.FC = ({ accountId, m } = useProfileSetupForm({ accountId, defaultValues, - onSuccess: () => router.push(rootPathnames.PROJECTS_LIST), + onSuccess, + onFailure, mode, }); @@ -99,9 +110,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m form.setFocus("name"); }, [form]); - const stringifiedDefaultValues = JSON.stringify(defaultValues); - const stringifiedValues = JSON.stringify(values); - const onCategoriesChange = useCallback( (categories: string[]) => updateCategories(categories), [updateCategories], @@ -119,10 +127,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m console.log({ defaultValues }); - const isThereDiff = useMemo(() => { - return stringifiedDefaultValues !== stringifiedValues; - }, [stringifiedValues, stringifiedDefaultValues]); - const getProjectEditorText = () => { if (socialDbSnapshot) { return values.isDao ? "Add proposal to update project" : "Update your project"; @@ -146,14 +150,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m console.log(form.formState.errors, form.formState.isValid); - // console.log({ stringifiedDefaultValues, stringifiedValues }); - - console.log("isThereAChange?", isThereDiff, { isSubmitting }); - - if (form.formState.isLoading) { - return ; - } - return (
@@ -320,14 +316,10 @@ export const ProfileSetupForm: React.FC = ({ accountId, m router.push(rootPathnames.PROJECTS_LIST); }} > - Cancel + {"Cancel"} -
diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index dcf929dc..222c3d40 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; @@ -6,7 +6,6 @@ import { ZodError } from "zod"; import type { ByAccountId } from "@/common/types"; import { useSession } from "@/entities/_shared/session"; -import { dispatch } from "@/store"; import { type ProfileSaveInputs, save } from "../models/effects"; import { addFundingSourceSchema, profileSetupSchema } from "../models/schemas"; @@ -14,7 +13,8 @@ import { AddFundingSourceInputs, ProfileSetupInputs } from "../models/types"; export type ProfileSetupFormParams = ByAccountId & Pick & { - onSuccess?: () => void; + onSuccess: () => void; + onFailure: (errorMessage: string) => void; defaultValues?: Partial; }; @@ -22,6 +22,7 @@ export const useProfileSetupForm = ({ accountId, mode, onSuccess, + onFailure, defaultValues, }: ProfileSetupFormParams) => { const [submitting, setSubmitting] = useState(false); @@ -29,7 +30,7 @@ export const useProfileSetupForm = ({ const [crossFieldErrors, setCrossFieldErrors] = useState>({}); const { isSignedIn } = useSession(); - const form = useForm({ + const self = useForm({ resolver: zodResolver(profileSetupSchema), mode: "onChange", defaultValues, @@ -40,7 +41,12 @@ export const useProfileSetupForm = ({ }, }); - const values = useWatch({ control: form.control }); + const values = useWatch({ control: self.control }); + + const isDisabled = useMemo( + () => !self.formState.isDirty || !self.formState.isValid || self.formState.isSubmitting, + [self.formState.isDirty, self.formState.isSubmitting, self.formState.isValid], + ); // Cross-field validation useEffect(() => { @@ -48,6 +54,7 @@ export const useProfileSetupForm = ({ .parseAsync(values) .then(() => setCrossFieldErrors({})) .catch((error: ZodError) => + // TODO: Consider using `setErrors` setCrossFieldErrors( error?.issues.reduce((errors, { code, message, path }) => { const fieldPath = path.at(0); @@ -61,77 +68,82 @@ export const useProfileSetupForm = ({ const updateBackgroundImage = useCallback( (url: string) => { - form.setValue("backgroundImage", url, { shouldValidate: true }); + self.setValue("backgroundImage", url, { shouldValidate: true }); }, - [form], + [self], ); const updateProfileImage = useCallback( (url: string) => { - form.setValue("profileImage", url, { shouldValidate: true }); + self.setValue("profileImage", url, { shouldValidate: true }); }, - [form], + [self], ); // Form update handlers const updateTeamMembers = useCallback( (members: string[]) => { - form.setValue("teamMembers", members, { shouldValidate: true }); + self.setValue("teamMembers", members, { shouldValidate: true }); }, - [form], + [self], ); const updateCategories = useCallback( (categories: string[]) => { - form.setValue("categories", categories, { shouldValidate: true }); + self.setValue("categories", categories, { shouldValidate: true }); }, - [form], + [self], ); const updateRepositories = useCallback( (repos: string[]) => { - form.setValue("githubRepositories", repos, { shouldValidate: true }); + self.setValue("githubRepositories", repos, { shouldValidate: true }); }, - [form], + [self], ); const addRepository = useCallback(() => { const currentRepos = values.githubRepositories || []; - form.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true }); - }, [form, values.githubRepositories]); - - const onSubmit: SubmitHandler = useCallback(async () => { - if (isSignedIn) { - setSubmitting(true); + self.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true }); + }, [self, values.githubRepositories]); + + const onSubmit: SubmitHandler = useCallback( + (inputs) => { + if (isSignedIn) { + setSubmitting(true); + + // TODO: pass `isDaoRepresentative` to this effect instead of storing `isDao` as a form field + save({ accountId, isDaoRepresentative: false, mode, data: inputs }) + .then((result) => { + if (result.success) { + onSuccess(); + } else { + onFailure(result.error); + } + }) + .catch((error) => { + console.error(error); + }); + } + }, - save({ mode }) - .then(async (result) => { - if (result.success) { - console.log("Opening wallet for approval..."); - } else { - dispatch.projectEditor.submissionStatus("pending"); - console.log("error while saving"); - } - }) - .catch((error) => { - console.error(error); - }); - } - }, [isSignedIn, mode]); + [accountId, isSignedIn, mode, onFailure, onSuccess], + ); return { form: { - ...form, + ...self, formState: { - ...form.formState, - errors: { ...form.formState.errors, ...crossFieldErrors }, + ...self.formState, + errors: { ...self.formState.errors, ...crossFieldErrors }, }, }, - errors: form.formState.errors, + errors: self.formState.errors, values, + isDisabled, isSubmitting: submitting, updateBackgroundImage, updateCategories, @@ -139,8 +151,8 @@ export const useProfileSetupForm = ({ updateRepositories, updateTeamMembers, addRepository, - onSubmit: form.handleSubmit(onSubmit), - resetForm: form.reset, + onSubmit: self.handleSubmit(onSubmit), + resetForm: self.reset, }; }; diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index b45438fc..b6668027 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -13,10 +13,11 @@ import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK } from "@/common/c import { socialDbContractClient } from "@/common/contracts/social"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import deepObjectDiff from "@/common/lib/deepObjectDiff"; -import { store } from "@/store"; +import type { ByAccountId } from "@/common/types"; import type { ProfileSetupMode } from "../types"; -import getSocialDataFormat from "../utils/getSocialDataFormat"; +import type { ProfileSetupInputs } from "./types"; +import { formInputsToSocialDbUpdateParams } from "../utils/normalization"; const getSocialData = async (accountId: string) => { try { @@ -28,36 +29,28 @@ const getSocialData = async (accountId: string) => { } }; -export type ProfileSaveInputs = { +export type ProfileSaveInputs = ByAccountId & { + isDaoRepresentative: boolean; mode: ProfileSetupMode; + data: ProfileSetupInputs; }; -export const save = async ({ mode }: ProfileSaveInputs) => { - const data = store.getState().projectEditor; - - const accountId = data.isDao ? data.daoAddress : data.accountId; - - if (!accountId) { - return { success: false, error: "No accountId provided" }; - } - - // If Dao, get dao policy - const daoPolicy = data.isDao ? await getDaoPolicy(accountId) : null; +export const save = async ({ isDaoRepresentative, accountId, mode, data }: ProfileSaveInputs) => { + const daoPolicy = isDaoRepresentative ? await getDaoPolicy(accountId) : null; // Validate DAO Address - const isDaoAddressValid = data.isDao ? validateNearAddress(data.daoAddress || "") : true; - - if (!isDaoAddressValid) { + if (isDaoRepresentative && !validateNearAddress(data.daoAddress || "")) { return { success: false, error: "DAO: Invalid NEAR account Id" }; } - // Social Data Format - const socialData = getSocialDataFormat(data); + const socialDbUpdateParams = formInputsToSocialDbUpdateParams(data); // If there is an existing social data, make the diff between then const existingSocialData = await getSocialData(accountId); - const diff = existingSocialData ? deepObjectDiff(existingSocialData, socialData) : socialData; + const diff = existingSocialData + ? deepObjectDiff(existingSocialData, socialDbUpdateParams) + : socialDbUpdateParams; const socialArgs = { data: { diff --git a/src/features/profile-setup/utils/getSocialDataFormat.ts b/src/features/profile-setup/utils/normalization.ts similarity index 91% rename from src/features/profile-setup/utils/getSocialDataFormat.ts rename to src/features/profile-setup/utils/normalization.ts index d8c32e74..8696ee35 100644 --- a/src/features/profile-setup/utils/getSocialDataFormat.ts +++ b/src/features/profile-setup/utils/normalization.ts @@ -1,6 +1,6 @@ -import { ProjectEditorState } from "../models"; +import type { ProfileSetupInputs } from "../models/types"; -const getSocialDataFormat = (data: ProjectEditorState) => { +export const formInputsToSocialDbUpdateParams = (data: ProfileSetupInputs) => { const body = { // Basic Profile details profile: { @@ -64,5 +64,3 @@ const getSocialDataFormat = (data: ProjectEditorState) => { return body; }; - -export default getSocialDataFormat; From 45f4ce09768a9963895aeef19a2a0d4eaacbada5 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Wed, 22 Jan 2025 15:13:51 +0000 Subject: [PATCH 34/84] wip --- src/features/profile-setup/hooks/forms.ts | 2 +- src/features/profile-setup/models/effects.ts | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 222c3d40..14d3fc6d 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -54,7 +54,7 @@ export const useProfileSetupForm = ({ .parseAsync(values) .then(() => setCrossFieldErrors({})) .catch((error: ZodError) => - // TODO: Consider using `setErrors` + // TODO: Consider using `setError` in forEach ( in the future ) setCrossFieldErrors( error?.issues.reduce((errors, { code, message, path }) => { const fieldPath = path.at(0); diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index b6668027..93990232 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -9,7 +9,12 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { LISTS_CONTRACT_ACCOUNT_ID, SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; import { naxiosInstance } from "@/common/api/near/client"; -import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK } from "@/common/constants"; +import { + FIFTY_TGAS, + FULL_TGAS, + MIN_PROPOSAL_DEPOSIT_FALLBACK, + PUBLIC_GOODS_REGISTRY_LIST_ID, +} from "@/common/constants"; import { socialDbContractClient } from "@/common/contracts/social"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import deepObjectDiff from "@/common/lib/deepObjectDiff"; @@ -58,10 +63,6 @@ export const save = async ({ isDaoRepresentative, accountId, mode, data }: Profi }, }; - const potlockRegistryArgs = { - list_id: 1, // hardcoding to potlock registry list for now - }; - // First, we have to check the account from social.near to see if it exists. If it doesn't, we need to add 0.1N to the deposit try { const account = await socialDbContractClient.getAccount({ accountId }); @@ -88,7 +89,11 @@ export const save = async ({ isDaoRepresentative, accountId, mode, data }: Profi // lists.potlock.near buildTransaction("register_batch", { receiverId: LISTS_CONTRACT_ACCOUNT_ID, - args: potlockRegistryArgs, + + args: { + list_id: PUBLIC_GOODS_REGISTRY_LIST_ID, + }, + deposit: parseNearAmount("0.05")!, gas: FULL_TGAS, }), From 8e19d89b428c3049924f3adf3e575a8f36a5852a Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 01:58:12 +0000 Subject: [PATCH 35/84] wip --- src/common/contracts/social/client.ts | 2 +- src/common/lib/deepObjectDiff.ts | 34 ----- src/common/lib/index.ts | 3 +- src/common/lib/object.ts | 46 +++++++ .../profile-setup/components/form.tsx | 1 - src/features/profile-setup/models/effects.ts | 123 ++++++++++-------- .../profile-setup/utils/normalization.ts | 89 ++++--------- 7 files changed, 144 insertions(+), 154 deletions(-) delete mode 100644 src/common/lib/deepObjectDiff.ts create mode 100644 src/common/lib/object.ts diff --git a/src/common/contracts/social/client.ts b/src/common/contracts/social/client.ts index b1419c75..c84d020e 100644 --- a/src/common/contracts/social/client.ts +++ b/src/common/contracts/social/client.ts @@ -99,7 +99,7 @@ export interface RegistrationSocialProfile { } type NEARSocialGetResponse = { - [key: string]: { + [key: AccountId]: { profile?: NEARSocialUserProfile; }; }; diff --git a/src/common/lib/deepObjectDiff.ts b/src/common/lib/deepObjectDiff.ts deleted file mode 100644 index 58402723..00000000 --- a/src/common/lib/deepObjectDiff.ts +++ /dev/null @@ -1,34 +0,0 @@ -const deepObjectDiff = (objOriginal: Record, objUpdated: Record) => { - if (!objUpdated) objUpdated = {}; - const diff = {}; - - function findDiff(original: any, updated: any, diffObj: any) { - Object.keys(updated).forEach((key) => { - const updatedValue = updated[key]; - const originalValue = original ? original[key] : undefined; - - // If both values are objects, recurse. - if ( - typeof updatedValue === "object" && - updatedValue !== null && - (originalValue === undefined || - (typeof originalValue === "object" && originalValue !== null)) - ) { - const nestedDiff = originalValue ? findDiff(originalValue, updatedValue, {}) : updatedValue; - - if (Object.keys(nestedDiff).length > 0) { - diffObj[key] = nestedDiff; - } - } else if (updatedValue !== originalValue) { - // Direct comparison for string values. - diffObj[key] = updatedValue; - } - }); - - return diffObj; - } - - return findDiff(objOriginal, objUpdated, diff); -}; - -export default deepObjectDiff; diff --git a/src/common/lib/index.ts b/src/common/lib/index.ts index c2534482..0e6f93cf 100644 --- a/src/common/lib/index.ts +++ b/src/common/lib/index.ts @@ -1,9 +1,10 @@ export * from "./string"; -export { formatWithCommas } from "./formatWithCommas"; +export * from "./formatWithCommas"; export * from "./format"; export * from "./id"; export * from "./navigation"; export * from "./daysAgo"; +export * from "./object"; export * from "./numeric"; export * from "./datetime"; export * from "./protocol-config"; diff --git a/src/common/lib/object.ts b/src/common/lib/object.ts new file mode 100644 index 00000000..711040ae --- /dev/null +++ b/src/common/lib/object.ts @@ -0,0 +1,46 @@ +type DeepPartial = T extends object + ? { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; + } + : T; + +// TODO: Find a way to resolve `any` without sacrificing type safety +export const deepObjectDiff = >( + objOriginal: T, + objUpdated: T, +): DeepPartial => { + if (!objUpdated) objUpdated = {} as T; + const diff = {} as DeepPartial; + + function findDiff>( + original: U | undefined, + updated: U, + diffObj: DeepPartial, + ): DeepPartial { + Object.keys(updated).forEach((key: string) => { + const updatedValue = updated[key]; + const originalValue = original ? original[key] : undefined; + + // If both values are objects, recurse. + if ( + typeof updatedValue === "object" && + updatedValue !== null && + (originalValue === undefined || + (typeof originalValue === "object" && originalValue !== null)) + ) { + const nestedDiff = originalValue ? findDiff(originalValue, updatedValue, {}) : updatedValue; + + if (Object.keys(nestedDiff).length > 0) { + (diffObj as Record)[key] = nestedDiff; + } + } else if (updatedValue !== originalValue) { + // Direct comparison for string values. + (diffObj as Record)[key] = updatedValue; + } + }); + + return diffObj; + } + + return findDiff(objOriginal, objUpdated, diff); +}; diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index 39059581..3e749f94 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -4,7 +4,6 @@ import { useRouter } from "next/router"; import { Form } from "react-hook-form"; import { Button, FormField } from "@/common/ui/components"; -import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index 93990232..fb1df3c1 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -15,14 +15,14 @@ import { MIN_PROPOSAL_DEPOSIT_FALLBACK, PUBLIC_GOODS_REGISTRY_LIST_ID, } from "@/common/constants"; -import { socialDbContractClient } from "@/common/contracts/social"; +import { type NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; -import deepObjectDiff from "@/common/lib/deepObjectDiff"; +import { deepObjectDiff } from "@/common/lib"; import type { ByAccountId } from "@/common/types"; import type { ProfileSetupMode } from "../types"; import type { ProfileSetupInputs } from "./types"; -import { formInputsToSocialDbUpdateParams } from "../utils/normalization"; +import { profileSetupFormInputsToSocialDbProfileUpdate } from "../utils/normalization"; const getSocialData = async (accountId: string) => { try { @@ -43,27 +43,56 @@ export type ProfileSaveInputs = ByAccountId & { export const save = async ({ isDaoRepresentative, accountId, mode, data }: ProfileSaveInputs) => { const daoPolicy = isDaoRepresentative ? await getDaoPolicy(accountId) : null; + const daoProposalDescription = + mode === "register" + ? "Create project on POTLOCK (2 steps: Register information on NEAR Social and register on POTLOCK)" + : "Update project on POTLOCK (via NEAR Social)"; + // Validate DAO Address if (isDaoRepresentative && !validateNearAddress(data.daoAddress || "")) { return { success: false, error: "DAO: Invalid NEAR account Id" }; } - const socialDbUpdateParams = formInputsToSocialDbUpdateParams(data); + const socialDbUpdateParams = profileSetupFormInputsToSocialDbProfileUpdate(data); // If there is an existing social data, make the diff between then const existingSocialData = await getSocialData(accountId); - const diff = existingSocialData - ? deepObjectDiff(existingSocialData, socialDbUpdateParams) + const nearSocialProfileDiff: NEARSocialUserProfile = existingSocialData + ? deepObjectDiff(existingSocialData, socialDbUpdateParams) : socialDbUpdateParams; const socialArgs = { data: { - [accountId]: diff, + [accountId]: { + profile: nearSocialProfileDiff, + + /** + *? Auto Follow and Star Potlock + */ + + index: { + star: { + key: { type: "social", path: `potlock.near/widget/Index` }, + value: { type: "star" }, + }, + + notify: { + key: "potlock.near", + value: { type: "star", item: { type: "social", path: `potlock.near/widget/Index` } }, + }, + }, + + graph: { + star: { ["potlock.near"]: { widget: { Index: "" } } }, + follow: { ["potlock.near"]: "" }, + }, + }, }, }; - // First, we have to check the account from social.near to see if it exists. If it doesn't, we need to add 0.1N to the deposit + // First, we have to check the account from social.near to see if it exists. + // If it doesn't, we need to add 0.1N to the deposit try { const account = await socialDbContractClient.getAccount({ accountId }); @@ -80,68 +109,53 @@ export const save = async ({ isDaoRepresentative, accountId, mode, data }: Profi deposit: parseNearAmount(depositFloat)!, }); - const transactions: Transaction[] = [socialTransaction]; - let daoTransactions: Transaction[] = []; + const transactions: Transaction[] = [socialTransaction]; - // if this is a creation action, we need to add the registry + // Submit registration to Public Goods Registry if (mode === "register") { transactions.push( // lists.potlock.near buildTransaction("register_batch", { receiverId: LISTS_CONTRACT_ACCOUNT_ID, - - args: { - list_id: PUBLIC_GOODS_REGISTRY_LIST_ID, - }, - + args: { list_id: PUBLIC_GOODS_REGISTRY_LIST_ID }, deposit: parseNearAmount("0.05")!, gas: FULL_TGAS, }), ); } - // if it is a DAO, we need to convert transactions to DAO function call proposals - if (data.isDao) { - daoTransactions = transactions.map((tx) => { - const action = { - method_name: tx.method, - gas: FIFTY_TGAS, - deposit: tx.deposit || "0", - args: Buffer.from(JSON.stringify(tx.args), "utf-8").toString("base64"), - }; - - return { - receiverId: data.daoAddress, - method: "add_proposal", - - args: { - proposal: { - description: - mode === "register" - ? "Create project on POTLOCK (2 steps: Register information on NEAR Social and register on POTLOCK)" - : "Update project on POTLOCK (via NEAR Social)", - - kind: { - FunctionCall: { - receiver_id: tx.receiverId, - actions: [action], + const callbackUrl = window.location.href; + + try { + // if it is a DAO, we need to convert transactions to DAO function call proposals + if (isDaoRepresentative) { + await naxiosInstance.contractApi().callMultiple( + transactions.map((tx) => { + const action = { + method_name: tx.method, + gas: FIFTY_TGAS, + deposit: tx.deposit || "0", + args: Buffer.from(JSON.stringify(tx.args), "utf-8").toString("base64"), + }; + + return { + receiverId: data.daoAddress, + method: "add_proposal", + + args: { + proposal: { + description: daoProposalDescription, + kind: { FunctionCall: { receiver_id: tx.receiverId, actions: [action] } }, }, }, - }, - }, - deposit: daoPolicy?.proposal_bond || MIN_PROPOSAL_DEPOSIT_FALLBACK, - gas: FULL_TGAS, - } as Transaction; - }); - } - - // Final registration step - const callbackUrl = `${location.origin}${location.pathname}?done=true`; + deposit: daoPolicy?.proposal_bond || MIN_PROPOSAL_DEPOSIT_FALLBACK, + gas: FULL_TGAS, + } as Transaction; + }), - try { - if (data.isDao) { - await naxiosInstance.contractApi().callMultiple(daoTransactions, callbackUrl); + callbackUrl, + ); } else { await naxiosInstance.contractApi().callMultiple(transactions, callbackUrl); } @@ -152,6 +166,7 @@ export const save = async ({ isDaoRepresentative, accountId, mode, data }: Profi }; } catch (e) { console.error(e); + return { success: false, error: "Error during the project registration.", diff --git a/src/features/profile-setup/utils/normalization.ts b/src/features/profile-setup/utils/normalization.ts index 8696ee35..6bb3fc1a 100644 --- a/src/features/profile-setup/utils/normalization.ts +++ b/src/features/profile-setup/utils/normalization.ts @@ -1,66 +1,29 @@ +import { pick } from "remeda"; + +import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } from "@/entities/_shared/account"; + import type { ProfileSetupInputs } from "../models/types"; -export const formInputsToSocialDbUpdateParams = (data: ProfileSetupInputs) => { - const body = { - // Basic Profile details - profile: { - // Background Image - ...(data.backgroundImage - ? { backgroundImage: data.backgroundImage } - : { backgroundImage: null }), - // Profile Image - ...(data.profileImage ? { image: data.profileImage } : { image: null }), - // Data - name: data.name, - plCategories: JSON.stringify(data.categories), - description: data.description, - plPublicGoodReason: data.publicGoodReason, - plSmartContracts: data.smartContracts ? JSON.stringify(data.smartContracts) : null, - plGithubRepos: JSON.stringify(data.githubRepositories), - plFundingSources: JSON.stringify(data.fundingSources), - linktree: { - website: data.website, - twitter: data.twitter, - telegram: data.telegram, - github: data.github, - }, - plTeam: JSON.stringify(data.teamMembers), - }, - // Auto Follow and Star Potlock - index: { - star: { - key: { - type: "social", - path: `potlock.near/widget/Index`, - }, - value: { - type: "star", - }, - }, - notify: { - key: "potlock.near", - value: { - type: "star", - item: { - type: "social", - path: `potlock.near/widget/Index`, - }, - }, - }, - }, - graph: { - star: { - ["potlock.near"]: { - widget: { - Index: "", - }, - }, - }, - follow: { - ["potlock.near"]: "", - }, - }, - }; +export const profileSetupFormInputsToSocialDbProfileUpdate = (inputs: ProfileSetupInputs) => ({ + /** + *? Standard NEAR Social profile details + */ + + ...pick(inputs, ["name", "description"]), + image: inputs.profileImage ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + ...(inputs.backgroundImage ? { backgroundImage: inputs.backgroundImage } : {}), + linktree: pick(inputs, ["website", "twitter", "telegram", "github"]), + + /** + *? POTLOCK-specific profile inputs + */ + + plCategories: JSON.stringify(inputs.categories), + plFundingSources: inputs.fundingSources ? JSON.stringify(inputs.fundingSources) : undefined, + + plGithubRepos: inputs.githubRepositories ? JSON.stringify(inputs.githubRepositories) : undefined, - return body; -}; + plPublicGoodReason: inputs.publicGoodReason, + plSmartContracts: inputs.smartContracts ? JSON.stringify(inputs.smartContracts) : undefined, + plTeam: JSON.stringify(inputs.teamMembers), +}); From 2ebc8e6673ab681c4b35caa94a872913f436993a Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 05:55:32 +0000 Subject: [PATCH 36/84] wip --- .vscode/settings.json | 6 + src/common/constants.ts | 42 ++++ src/common/contracts/social/hooks.ts | 7 +- .../account/components/AccountGroup.tsx | 19 +- .../components/AccountGroupEditModal.tsx | 6 +- src/entities/_shared/account/constants.ts | 11 +- .../_shared/account/hooks/social-profile.ts | 2 +- src/entities/_shared/account/types.ts | 12 +- src/entities/_shared/session/hooks/session.ts | 10 +- src/entities/_shared/session/types.ts | 2 + src/entities/list/hooks/useListForm.ts | 8 +- .../AddTeamMembersModal/components.tsx | 46 ---- .../components/AddTeamMembersModal/index.tsx | 91 -------- .../profile-setup/components/Repositories.tsx | 83 ------- .../profile-setup/components/SubHeader.tsx | 22 -- ...tContractModal.tsx => contracts-modal.tsx} | 46 ++-- ...artContracts.tsx => contracts-section.tsx} | 57 ++--- .../{DAOInProgress.tsx => dao-progress.tsx} | 21 +- .../components/form-elements.tsx | 61 ++--- .../profile-setup/components/form.tsx | 220 +++++++++--------- ...ingSourceTable.tsx => funding-sources.tsx} | 26 +-- .../{SocialLinks.tsx => linktree-section.tsx} | 17 +- .../components/repositories-section.tsx | 75 ++++++ src/features/profile-setup/hooks/forms.ts | 100 ++++---- .../hooks/useInitProjectState.ts | 144 ------------ src/features/profile-setup/index.ts | 2 - .../models/{index.ts => deprecated.ts} | 4 + src/features/profile-setup/models/effects.ts | 54 ++--- src/features/profile-setup/models/schemas.ts | 29 +-- .../profile-setup/utils/normalization.ts | 4 +- .../pot/_deprecated}/ErrorModal.tsx | 9 +- .../pot/_deprecated}/SuccessModal.tsx | 7 +- src/layout/pot/components/layout.tsx | 7 +- src/pages/profile/edit.tsx | 36 ++- src/pages/register.tsx | 36 ++- src/store/models.ts | 3 - 36 files changed, 523 insertions(+), 802 deletions(-) delete mode 100644 src/features/profile-setup/components/AddTeamMembersModal/components.tsx delete mode 100644 src/features/profile-setup/components/AddTeamMembersModal/index.tsx delete mode 100644 src/features/profile-setup/components/Repositories.tsx delete mode 100644 src/features/profile-setup/components/SubHeader.tsx rename src/features/profile-setup/components/{EditSmartContractModal.tsx => contracts-modal.tsx} (71%) rename src/features/profile-setup/components/{SmartContracts.tsx => contracts-section.tsx} (71%) rename src/features/profile-setup/components/{DAOInProgress.tsx => dao-progress.tsx} (67%) rename src/features/profile-setup/components/{FundingSourceTable.tsx => funding-sources.tsx} (87%) rename src/features/profile-setup/components/{SocialLinks.tsx => linktree-section.tsx} (84%) create mode 100644 src/features/profile-setup/components/repositories-section.tsx delete mode 100644 src/features/profile-setup/hooks/useInitProjectState.ts rename src/features/profile-setup/models/{index.ts => deprecated.ts} (98%) rename src/{features/profile-setup/components => layout/pot/_deprecated}/ErrorModal.tsx (82%) rename src/{features/profile-setup/components => layout/pot/_deprecated}/SuccessModal.tsx (85%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2fbb3042..5b08e324 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,6 +45,7 @@ "errorLens.warningGutterIconColor": "#ff942f", "cSpell.words": [ "amichaeltest", + "Aptos", "Attributify", "bitget", "builddao", @@ -58,14 +59,17 @@ "datetime", "defi", "dontcare", + "Elrond", "fastnear", "foodbank", "formkit", "graylisted", "hookform", + "Injective", "intear", "Jikugodwill", "kubb", + "Linea", "linktree", "METAPOOL", "mpdao", @@ -78,6 +82,7 @@ "openapi", "overscan", "partialize", + "Polkadot", "potfactory", "POTLOCK", "precommit", @@ -89,6 +94,7 @@ "sessionid", "shadcn", "socialdb", + "Solana", "SOURCECODE", "stnear", "svgr", diff --git a/src/common/constants.ts b/src/common/constants.ts index ea3ba782..2995573a 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -61,6 +61,48 @@ export const APP_METADATA: Metadata & { }, }; +export const CHAIN_OPTIONS: Record = { + NEAR: { isEVM: false }, + Solana: { isEVM: false }, + Ethereum: { isEVM: true }, + Polygon: { isEVM: true }, + Avalanche: { isEVM: true }, + Optimism: { isEVM: true }, + Arbitrum: { isEVM: true }, + BNB: { isEVM: true }, + Sui: { isEVM: false }, + Aptos: { isEVM: false }, + Polkadot: { isEVM: false }, + Stellar: { isEVM: false }, + + // Note: ZkSync aims for EVM compatibility but might not fully be considered as traditional EVM + // at the time of writing. + ZkSync: { isEVM: false }, + + Celo: { isEVM: true }, + Aurora: { isEVM: true }, + Injective: { isEVM: true }, + Base: { isEVM: false }, + + // Listed twice in the original list; included once here. + Manta: { isEVM: false }, + + Fantom: { isEVM: true }, + ZkEVM: { isEVM: true }, + Flow: { isEVM: false }, + Tron: { isEVM: true }, + + // Formerly known as Elrond, not traditionally EVM but has some level of compatibility. + MultiverseX: { isEVM: false }, + + // Assuming EVM compatibility based on the context of ZkEVM. + Scroll: { isEVM: true }, + + // Assuming non-EVM due to lack of information. + Linea: { isEVM: true }, + Metis: { isEVM: true }, +}; + export const TOTAL_FEE_BASIS_POINTS = 10_000; export const TOP_LEVEL_ROOT_ACCOUNT_ID = NETWORK === "mainnet" ? "near" : "testnet"; export const NATIVE_TOKEN_ID = "near"; diff --git a/src/common/contracts/social/hooks.ts b/src/common/contracts/social/hooks.ts index 3b132f38..d72f66dd 100644 --- a/src/common/contracts/social/hooks.ts +++ b/src/common/contracts/social/hooks.ts @@ -9,10 +9,5 @@ export const useSocialProfile = ({ accountId, }: ByAccountId & ConditionalActivation) => useSWR(["useSocialProfile", accountId], ([_queryKey, account_id]) => - !enabled - ? undefined - : contractClient - .getSocialProfile({ accountId: account_id }) - //? Handling `null` response - .then((response) => response ?? undefined), + !enabled ? undefined : contractClient.getSocialProfile({ accountId: account_id }), ); diff --git a/src/entities/_shared/account/components/AccountGroup.tsx b/src/entities/_shared/account/components/AccountGroup.tsx index 7f3f5642..eeba4499 100644 --- a/src/entities/_shared/account/components/AccountGroup.tsx +++ b/src/entities/_shared/account/components/AccountGroup.tsx @@ -35,7 +35,6 @@ export const AccountGroup: React.FC = ({ const { value: accountIds } = props; const isEditingEnabled = isEditable && "title" in props; const modalId = useId(); - const openAccountsModal = useCallback(() => show(modalId), [modalId]); const accountList = useMemo( @@ -50,24 +49,36 @@ export const AccountGroup: React.FC = ({ {...{ accountId: account.accountId }} /> ))} + {accountIds.length > 4 && (
{accountIds.length - 4}+ -
+ +
{accountIds.slice(4).map((account) => ( {account.accountId} diff --git a/src/entities/_shared/account/components/AccountGroupEditModal.tsx b/src/entities/_shared/account/components/AccountGroupEditModal.tsx index e9522dc2..0e0b93ee 100644 --- a/src/entities/_shared/account/components/AccountGroupEditModal.tsx +++ b/src/entities/_shared/account/components/AccountGroupEditModal.tsx @@ -23,13 +23,13 @@ import { } from "@/common/ui/components"; import { TextField } from "@/common/ui/form-fields"; import { cn } from "@/common/ui/utils"; -import { AccountKey, AccountListItem, validAccountId } from "@/entities/_shared/account"; +import { AccountGroupItem, AccountListItem, validAccountId } from "@/entities/_shared/account"; export type AccountGroupEditModalProps = { title: string; - value: AccountKey[]; + value: AccountGroupItem[]; onSubmit: (accountIds: AccountId[]) => void; - handleRemoveAccounts?: (accounts: AccountKey[]) => void; + handleRemoveAccounts?: (accounts: AccountGroupItem[]) => void; footer?: React.ReactNode; maxAccounts?: number; }; diff --git a/src/entities/_shared/account/constants.ts b/src/entities/_shared/account/constants.ts index 8a2b20e9..244b9d21 100644 --- a/src/entities/_shared/account/constants.ts +++ b/src/entities/_shared/account/constants.ts @@ -1,11 +1,20 @@ import { IMAGES_ASSET_ENDPOINT_URL } from "@/common/constants"; import { RegistrationStatus } from "@/common/contracts/core"; +import type { AccountProfileLinktreeKey } from "./types"; + export const ACCOUNT_PROFILE_IMAGE_PLACEHOLDER_SRC = `${IMAGES_ASSET_ENDPOINT_URL}/profile-image.png`; export const ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC = `${IMAGES_ASSET_ENDPOINT_URL}/profile-banner.png`; -export const ACCOUNT_PROFILE_URL_PATTERNS = { +export const ACCOUNT_PROFILE_LINKTREE_KEYS: AccountProfileLinktreeKey[] = [ + "github", + "telegram", + "twitter", + "website", +]; + +export const ACCOUNT_PROFILE_URL_PATTERNS: Record = { github: /^(?:https?:\/\/)?(?:www\.)?github\.com\/([^/]+(?:\/[^/]+)?)\/?$/, twitter: /^(?:https?:\/\/)?(?:www\.)?x\.com\/([^/]+(?:\/[^/]+)?)\/?$/, telegram: /^(?:https?:\/\/)?(?:www\.)?t\.com\/([^/]+(?:\/[^/]+)?)\/?$/, diff --git a/src/entities/_shared/account/hooks/social-profile.ts b/src/entities/_shared/account/hooks/social-profile.ts index 14e3c855..7abc18c7 100644 --- a/src/entities/_shared/account/hooks/social-profile.ts +++ b/src/entities/_shared/account/hooks/social-profile.ts @@ -40,7 +40,7 @@ export const useAccountSocialProfile = ({ return { isLoading, - profile: data, + profile: data ?? undefined, avatarSrc, backgroundSrc, error, diff --git a/src/entities/_shared/account/types.ts b/src/entities/_shared/account/types.ts index 170e17be..e4c928fa 100644 --- a/src/entities/_shared/account/types.ts +++ b/src/entities/_shared/account/types.ts @@ -1,7 +1,9 @@ +import type { ProfileLinktree } from "@/common/contracts/social"; import { ByAccountId, ByRegistrationId } from "@/common/types"; -export type AccountKey = { - accountId: string; - registrationId?: number; - isNew?: boolean; -}; +export type AccountGroupItem = ByAccountId & + Partial & { + isNew?: boolean; + }; + +export type AccountProfileLinktreeKey = keyof ProfileLinktree; diff --git a/src/entities/_shared/session/hooks/session.ts b/src/entities/_shared/session/hooks/session.ts index aeabdd85..529c41f4 100644 --- a/src/entities/_shared/session/hooks/session.ts +++ b/src/entities/_shared/session/hooks/session.ts @@ -14,11 +14,13 @@ import { UserSession } from "../types"; export const useSession = (): UserSession => { const { isSignedIn, wallet } = useWallet(); const { actAsDao, accountId: lastActiveAccountId } = useGlobalStoreSelector(prop("nav")); - const asDao = actAsDao.toggle && Boolean(actAsDao.defaultAddress); + const isDaoRepresentative = actAsDao.toggle && Boolean(actAsDao.defaultAddress); const accountId: AccountId | undefined = useMemo( - () => (asDao ? actAsDao.defaultAddress : (wallet?.accountId ?? lastActiveAccountId)), - [actAsDao.defaultAddress, asDao, lastActiveAccountId, wallet?.accountId], + () => + isDaoRepresentative ? actAsDao.defaultAddress : (wallet?.accountId ?? lastActiveAccountId), + + [actAsDao.defaultAddress, isDaoRepresentative, lastActiveAccountId, wallet?.accountId], ); /** @@ -45,6 +47,7 @@ export const useSession = (): UserSession => { return { accountId, isSignedIn: true, + isDaoRepresentative, isMetadataLoading: isRegistrationFlagLoading || isRegistrationLoading, hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, registrationStatus: registration?.status, @@ -54,6 +57,7 @@ export const useSession = (): UserSession => { accountId: undefined, registrationStatus: undefined, isSignedIn: false, + isDaoRepresentative: false, isMetadataLoading: false, hasRegistrationApproved: false, }; diff --git a/src/entities/_shared/session/types.ts b/src/entities/_shared/session/types.ts index cc1198c5..5e43cd3b 100644 --- a/src/entities/_shared/session/types.ts +++ b/src/entities/_shared/session/types.ts @@ -10,6 +10,7 @@ export type UserSession = accountId: AccountId; registrationStatus?: RegistrationStatus; isSignedIn: true; + isDaoRepresentative: boolean; isMetadataLoading: boolean; hasRegistrationApproved: boolean; } @@ -17,6 +18,7 @@ export type UserSession = accountId: undefined; registrationStatus: undefined; isSignedIn: false; + isDaoRepresentative: false; isMetadataLoading: false; hasRegistrationApproved: false; }; diff --git a/src/entities/list/hooks/useListForm.ts b/src/entities/list/hooks/useListForm.ts index f18f6dd4..8caccbd2 100644 --- a/src/entities/list/hooks/useListForm.ts +++ b/src/entities/list/hooks/useListForm.ts @@ -9,7 +9,7 @@ import { naxiosInstance } from "@/common/api/near/client"; import { listsContractClient } from "@/common/contracts/core"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; -import { AccountKey, validateAccountId } from "@/entities/_shared/account"; +import { AccountGroupItem, validateAccountId } from "@/entities/_shared/account"; import { dispatch } from "@/store"; import { ListFormModalType } from "../types"; @@ -73,11 +73,11 @@ export const useListForm = () => { }); }; - const handleUnRegisterAccount = (registrants: AccountKey[]) => { + const handleUnRegisterAccount = (registrants: AccountGroupItem[]) => { if (!id) return; const allTransactions: any = []; - registrants.map((registrant: AccountKey) => { + registrants.map((registrant: AccountGroupItem) => { allTransactions.push( buildTransaction("unregister", { receiverId: LISTS_CONTRACT_ACCOUNT_ID, @@ -108,7 +108,7 @@ export const useListForm = () => { }); }; - const handleRemoveAdmin = (accounts: AccountKey[]) => { + const handleRemoveAdmin = (accounts: AccountGroupItem[]) => { const accountIds = accounts.map(prop("accountId")); listsContractClient diff --git a/src/features/profile-setup/components/AddTeamMembersModal/components.tsx b/src/features/profile-setup/components/AddTeamMembersModal/components.tsx deleted file mode 100644 index 22765d80..00000000 --- a/src/features/profile-setup/components/AddTeamMembersModal/components.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback } from "react"; - -import { Button } from "@/common/ui/components"; -import { AccountProfilePicture } from "@/entities/_shared/account"; -import { dispatch } from "@/store"; - -const Item = ({ - accountId, - onRemove, -}: { - accountId: string; - onRemove: (accountId: string) => void; -}) => { - return ( -
-
-
- -

@{accountId}

-
- - -
-
- ); -}; - -type AccountItemProps = { - accountIds: string[]; -}; - -export const AccountItems = ({ accountIds }: AccountItemProps) => { - const removeAccountHandler = useCallback((accountId: string) => { - dispatch.projectEditor.removeTeamMember(accountId); - }, []); - - return ( - <> - {accountIds.map((accountId) => ( - - ))} - - ); -}; diff --git a/src/features/profile-setup/components/AddTeamMembersModal/index.tsx b/src/features/profile-setup/components/AddTeamMembersModal/index.tsx deleted file mode 100644 index 9fcbd6f0..00000000 --- a/src/features/profile-setup/components/AddTeamMembersModal/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; - -import { validateNearAddress } from "@wpdas/naxios"; -import { MdGroup } from "react-icons/md"; - -import { Button, Dialog, DialogContent, Input } from "@/common/ui/components"; -import { dispatch, useGlobalStoreSelector } from "@/store"; - -import { AccountItems } from "./components"; - -type Props = { - open?: boolean; - onCloseClick?: () => void; - onMembersChange?: (members: string[]) => void; -}; - -const AddTeamMembersModal = ({ open, onCloseClick, onMembersChange }: Props) => { - const members = useGlobalStoreSelector((state) => state.projectEditor.teamMembers); - const [account, setAccount] = useState(""); - const [invalidNearAcc, setInvalidNearAcc] = useState(false); - - const addMemberHandler = useCallback(() => { - if (!validateNearAddress(account)) { - setInvalidNearAcc(true); - return; - } - - dispatch.projectEditor.addTeamMember(account); - setAccount(""); - }, [account]); - - useEffect(() => { - if (onMembersChange) { - onMembersChange(members); - } - }, [members, onMembersChange]); - - return ( - - -
-
- -
-

Add team members

-
- - {/* Description */} -
-

- Add NEAR account IDs for your team members. -

-
- { - if (invalidNearAcc) { - setInvalidNearAcc(false); - } - - setAccount(e.target.value); - }} - onKeyUp={(e) => { - if (e.key === "Enter") { - addMemberHandler(); - } - }} - placeholder="NEAR account ID" - className="focus-visible:ring-none rounded-r-[0] focus-visible:ring-opacity-0" - error={invalidNearAcc ? "Invalid NEAR Account ID" : ""} - /> - -
-

- {members.length} - {members.length > 1 ? "members" : "member"} -

- -
-
-
- ); -}; - -export default AddTeamMembersModal; diff --git a/src/features/profile-setup/components/Repositories.tsx b/src/features/profile-setup/components/Repositories.tsx deleted file mode 100644 index 6a43eb0a..00000000 --- a/src/features/profile-setup/components/Repositories.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { ChangeEvent, useCallback, useEffect, useState } from "react"; - -import { extractFromUrl } from "@/common/lib"; -import { ACCOUNT_PROFILE_URL_PATTERNS } from "@/entities/_shared/account"; -import { dispatch, useGlobalStoreSelector } from "@/store"; - -import { CustomInput } from "./form-elements"; - -const Repo = ({ - repo, - index, - onChangeHandler, -}: { - repo: string; - index: number; - onChangeHandler: (repoIndex: number, value: string) => void; -}) => { - const [fieldValue, setValue] = useState(repo?.replace("https://github.com/", "") || ""); - - const onChange = (e: ChangeEvent) => { - setValue(extractFromUrl(e.target.value, ACCOUNT_PROFILE_URL_PATTERNS.github) || ""); - }; - - return ( - { - onChangeHandler(index, fieldValue ? `https://github.com/${fieldValue}` : ""); - }, - }} - /> - ); -}; - -type Props = { - onChange?: (repositories: string[]) => void; -}; - -const Repositories = ({ onChange }: Props) => { - const repositories = useGlobalStoreSelector( - (state) => state.projectEditor.githubRepositories || [], - ); - - const [repos, setRepos] = useState(repositories.length > 0 ? repositories : [""]); - - useEffect(() => { - setRepos(repositories); - }, [repositories]); - - const onChangeHandler = useCallback( - (repoIndex: number, value: string) => { - const updatedState = [...repositories]; - updatedState[repoIndex] = value; - setRepos(updatedState); - dispatch.projectEditor.updateRepositories(updatedState); - }, - [repositories], - ); - - useEffect(() => { - if (onChange) { - onChange(repos); - } - }, [repos, onChange]); - - const toShow = repositories.length > 0 ? repositories : [""]; - - return ( - <> - {toShow.map((repo, index) => ( - - ))} - - ); -}; - -export default Repositories; diff --git a/src/features/profile-setup/components/SubHeader.tsx b/src/features/profile-setup/components/SubHeader.tsx deleted file mode 100644 index ed94e0af..00000000 --- a/src/features/profile-setup/components/SubHeader.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { SubTitle } from "./styles"; - -const SubHeader = ({ - title, - required, - className, -}: { - title: string; - required?: boolean; - className?: string; -}) => ( - - {title} - {required ? ( - Required - ) : ( - Optional - )} - -); - -export default SubHeader; diff --git a/src/features/profile-setup/components/EditSmartContractModal.tsx b/src/features/profile-setup/components/contracts-modal.tsx similarity index 71% rename from src/features/profile-setup/components/EditSmartContractModal.tsx rename to src/features/profile-setup/components/contracts-modal.tsx index 72e1b8e7..b93b83b1 100644 --- a/src/features/profile-setup/components/EditSmartContractModal.tsx +++ b/src/features/profile-setup/components/contracts-modal.tsx @@ -3,43 +3,42 @@ import { useCallback, useEffect, useState } from "react"; import { validateNearAddress } from "@wpdas/naxios"; import { CircleAlert } from "lucide-react"; +import { CHAIN_OPTIONS } from "@/common/constants"; import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@/common/ui/components"; -import { dispatch, useGlobalStoreSelector } from "@/store"; +import { AddChainSelector } from "./contracts-section"; import { CustomInput } from "./form-elements"; -import { AddChainSelector, CHAIN_OPTIONS } from "./SmartContracts"; +import type { ProfileSetupInputs } from "../models/types"; import validateEVMAddress from "../utils/validateEVMAddress"; -type Props = { +export type ProfileSetupSmartContractModalProps = { + data: ProfileSetupInputs["smartContracts"]; open?: boolean; onCloseClick?: () => void; contractIndex: number; }; -const EditSmartContractModal = ({ open, onCloseClick, contractIndex }: Props) => { - const contracts = useGlobalStoreSelector( - (state) => state.projectEditor.smartContracts || [["", ""]], - ); - +export const ProfileSetupSmartContractModal: React.FC = ({ + data = [], + open, + onCloseClick, + contractIndex, +}) => { const [chain, setChain] = useState( - contracts[contractIndex] && contracts[contractIndex][0] ? contracts[contractIndex][0] : "", + data[contractIndex] && data[contractIndex][0] ? data[contractIndex][0] : "", ); const [address, setAddress] = useState( - contracts[contractIndex] && contracts[contractIndex][1] ? contracts[contractIndex][1] : "", + data[contractIndex] && data[contractIndex][1] ? data[contractIndex][1] : "", ); const [error, setError] = useState(""); useEffect(() => { - setChain( - contracts[contractIndex] && contracts[contractIndex][0] ? contracts[contractIndex][0] : "", - ); + setChain(data[contractIndex] && data[contractIndex][0] ? data[contractIndex][0] : ""); - setAddress( - contracts[contractIndex] && contracts[contractIndex][0] ? contracts[contractIndex][1] : "", - ); - }, [contractIndex, contracts]); + setAddress(data[contractIndex] && data[contractIndex][0] ? data[contractIndex][1] : ""); + }, [contractIndex, data]); const saveHandler = useCallback(() => { const isEVM = CHAIN_OPTIONS[chain].isEVM; @@ -56,16 +55,17 @@ const EditSmartContractModal = ({ open, onCloseClick, contractIndex }: Props) => return; } + // TODO: Don't forget to rewrite // Update contract info in the store - dispatch.projectEditor.editSmartContract({ - data: [chain, address], - contractIndex, - }); + // dispatch.projectEditor.editSmartContract({ + // data: [chain, address], + // contractIndex, + // }); if (onCloseClick) { onCloseClick(); } - }, [chain, address, contractIndex, onCloseClick]); + }, [chain, address, onCloseClick]); return ( @@ -119,5 +119,3 @@ const EditSmartContractModal = ({ open, onCloseClick, contractIndex }: Props) => ); }; - -export default EditSmartContractModal; diff --git a/src/features/profile-setup/components/SmartContracts.tsx b/src/features/profile-setup/components/contracts-section.tsx similarity index 71% rename from src/features/profile-setup/components/SmartContracts.tsx rename to src/features/profile-setup/components/contracts-section.tsx index 7e86eea7..761eb16f 100644 --- a/src/features/profile-setup/components/SmartContracts.tsx +++ b/src/features/profile-setup/components/contracts-section.tsx @@ -3,6 +3,7 @@ import { useCallback, useState } from "react"; import { validateNearAddress } from "@wpdas/naxios"; import { CircleAlert } from "lucide-react"; +import { CHAIN_OPTIONS } from "@/common/constants"; import { Button, Select, @@ -13,9 +14,9 @@ import { } from "@/common/ui/components"; import Delete from "@/common/ui/svg/Delete"; import Edit from "@/common/ui/svg/Edit"; -import { dispatch, useGlobalStoreSelector } from "@/store"; import { CustomInput, Label } from "./form-elements"; +import type { ProfileSetupInputs } from "../models/types"; import validateEVMAddress from "../utils/validateEVMAddress"; type AddChainSelectorProps = { @@ -26,35 +27,6 @@ type AddChainSelectorProps = { chainLabel?: string; }; -export const CHAIN_OPTIONS: Record = { - NEAR: { isEVM: false }, - Solana: { isEVM: false }, - Ethereum: { isEVM: true }, - Polygon: { isEVM: true }, - Avalanche: { isEVM: true }, - Optimism: { isEVM: true }, - Arbitrum: { isEVM: true }, - BNB: { isEVM: true }, - Sui: { isEVM: false }, - Aptos: { isEVM: false }, - Polkadot: { isEVM: false }, - Stellar: { isEVM: false }, - ZkSync: { isEVM: false }, // Note: ZkSync aims for EVM compatibility but might not fully be considered as traditional EVM at the time of writing. - Celo: { isEVM: true }, - Aurora: { isEVM: true }, - Injective: { isEVM: true }, - Base: { isEVM: false }, - Manta: { isEVM: false }, // Listed twice in the original list; included once here. - Fantom: { isEVM: true }, - ZkEVM: { isEVM: true }, // Considering the name, assuming it aims for EVM compatibility. - Flow: { isEVM: false }, - Tron: { isEVM: true }, - MultiverseX: { isEVM: false }, // Formerly known as Elrond, not traditionally EVM but has some level of compatibility. - Scroll: { isEVM: true }, // Assuming EVM compatibility based on the context of ZkEVM. - Linea: { isEVM: true }, // Assuming non-EVM due to lack of information. - Metis: { isEVM: true }, -}; - export const AddChainSelector = ({ onChange, defaultValue, @@ -127,14 +99,14 @@ const SmartContract = ({ return; } - dispatch.projectEditor.addSmartContract([chain, address], index); + // dispatch.projectEditor.addSmartContract([chain, address], index); setChain(""); setAddress(""); } }, [chain, address, index]); const onRemoveHandler = useCallback(() => { - dispatch.projectEditor.removeSmartContract(index); + // dispatch.projectEditor.removeSmartContract(index); }, [index]); return ( @@ -207,17 +179,19 @@ const SmartContract = ({ ); }; -type SmartContractsProps = { +export type ProfileSetupSmartContractsSectionProps = { + values: ProfileSetupInputs["smartContracts"]; onEditClickHandler: (contractIndex: number) => void; }; -export const SmartContracts = ({ onEditClickHandler }: SmartContractsProps) => { - const smartContracts = useGlobalStoreSelector((state) => state.projectEditor.smartContracts); - - if (smartContracts && smartContracts.length > 0) { +export const ProfileSetupSmartContractsSection = ({ + values, + onEditClickHandler, +}: ProfileSetupSmartContractsSectionProps) => { + if (values && values.length > 0) { return ( <> - {smartContracts.map((contractInfo, index) => ( + {values.map((contractInfo, index) => ( { isPreview /> ))} - + + ); - } - - return ; + } else return ; }; diff --git a/src/features/profile-setup/components/DAOInProgress.tsx b/src/features/profile-setup/components/dao-progress.tsx similarity index 67% rename from src/features/profile-setup/components/DAOInProgress.tsx rename to src/features/profile-setup/components/dao-progress.tsx index f23552a0..8a0ef3c9 100644 --- a/src/features/profile-setup/components/DAOInProgress.tsx +++ b/src/features/profile-setup/components/dao-progress.tsx @@ -1,15 +1,22 @@ import Link from "next/link"; import { Button } from "@/common/ui/components"; -import { useGlobalStoreSelector } from "@/store"; -const DAOInProgress = () => { - const { daoProjectProposal, daoAddress } = useGlobalStoreSelector((state) => state.projectEditor); +export const ProfileSetupDaoProgress = () => { + const { daoProjectProposal, daoAddress } = { + daoProjectProposal: { id: 0 }, + daoAddress: undefined, + }; // useGlobalStoreSelector((state) => state.projectEditor); if (!daoProjectProposal || !daoAddress) { return ""; } + const proposalHref = + "https://near.org/sking.near/widget/DAO.Page?daoId=" + + daoAddress + + `&tab=proposal&proposalId=${daoProjectProposal.id}`; + return (
{ NB: This proposal consists of 2 steps (individual proposals): Register information on NEAR Social and register on POTLOCK.

- +
); }; - -export default DAOInProgress; diff --git a/src/features/profile-setup/components/form-elements.tsx b/src/features/profile-setup/components/form-elements.tsx index 6ef15abd..31c661ca 100644 --- a/src/features/profile-setup/components/form-elements.tsx +++ b/src/features/profile-setup/components/form-elements.tsx @@ -12,15 +12,34 @@ import { Textarea, } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { AccountProfilePicture } from "@/entities/_shared/account"; import { ProjectCategoryVariant } from "@/entities/project"; -import { useGlobalStoreSelector } from "@/store"; -export const Row = ({ children }: { children: JSX.Element | JSX.Element[] }) => ( +import { SubTitle } from "./styles"; + +export const SubHeader = ({ + title, + required, + className, +}: { + title: string; + required?: boolean; + className?: string; +}) => ( + + {title} + {required ? ( + Required + ) : ( + Optional + )} + +); + +export const Row = ({ children }: { children: React.ReactNode }) => (
{children}
); -export const InputContainer = ({ children }: { children: JSX.Element | JSX.Element[] }) => ( +export const InputContainer = ({ children }: { children: React.ReactNode }) => (
{children}
@@ -181,37 +200,3 @@ export const CustomTextForm = ({
); }; - -const MAX_DISPLAYED_MEMBERS = 5; - -export const AccountStack = () => { - const members = useGlobalStoreSelector((state) => state.projectEditor.teamMembers); - const displayedMembers = members.slice(0, MAX_DISPLAYED_MEMBERS); - const hiddenItemsCount = Math.max(members.length - MAX_DISPLAYED_MEMBERS, 0); - - return ( -
- {hiddenItemsCount > 0 && ( -
- - {`+${hiddenItemsCount}`} - -
- )} - - {displayedMembers.map((memberAccountId, index) => ( - 0 || (index === 0 && hiddenItemsCount > 0), - })} - /> - ))} -
- ); -}; diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index 3e749f94..a70c03ec 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -2,108 +2,127 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; import { Form } from "react-hook-form"; +import { pick } from "remeda"; import { Button, FormField } from "@/common/ui/components"; -import { useToast } from "@/common/ui/hooks"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + ACCOUNT_PROFILE_LINKTREE_KEYS, + AccountGroup, useAccountSocialProfile, } from "@/entities/_shared/account"; import { rootPathnames } from "@/pathnames"; import AddFundingSourceModal from "./AddFundingSourceModal"; -import AddTeamMembersModal from "./AddTeamMembersModal"; -import EditSmartContractModal from "./EditSmartContractModal"; +import { ProfileSetupSmartContractModal } from "./contracts-modal"; +import { ProfileSetupSmartContractsSection } from "./contracts-section"; import { - AccountStack, CustomInput, CustomTextForm, ProjectCategoryPicker, Row, + SubHeader, } from "./form-elements"; -import FundingSourceTable from "./FundingSourceTable"; +import { ProfileSetupFundingSourcesTable } from "./funding-sources"; import { ProfileSetupImageUpload } from "./image-upload"; -import Repositories from "./Repositories"; -import { SmartContracts } from "./SmartContracts"; -import SocialLinks from "./SocialLinks"; +import { ProfileSetupLinktreeSection } from "./linktree-section"; +import { ProfileSetupRepositoriesSection } from "./repositories-section"; import { LowerBannerContainer, LowerBannerContainerLeft } from "./styles"; -import SubHeader from "./SubHeader"; import { type ProfileSetupFormParams, useProfileSetupForm } from "../hooks/forms"; import { ProfileSetupInputs } from "../models/types"; -export type ProfileSetupFormProps = Pick & {}; - -export const ProfileSetupForm: React.FC = ({ accountId, mode }) => { +export type ProfileSetupFormProps = Pick< + ProfileSetupFormParams, + "mode" | "accountId" | "isDaoRepresentative" | "onSuccess" | "onFailure" +> & {}; + +export const ProfileSetupForm: React.FC = ({ + mode, + accountId, + isDaoRepresentative, + onSuccess, + onFailure, +}) => { const router = useRouter(); - const { toast } = useToast(); - const [addTeamModalOpen, setAddTeamModalOpen] = useState(false); const [addFundingModalOpen, setAddFundingModalOpen] = useState(false); const [editFundingIndex, setEditFundingIndex] = useState(); const [editContractIndex, setEditContractIndex] = useState(); const { - profile: socialDbSnapshot, + profile: socialProfileSnapshot = null, avatarSrc, backgroundSrc, } = useAccountSocialProfile({ accountId }); const defaultValues = useMemo>( () => - socialDbSnapshot === undefined + socialProfileSnapshot === null ? { profileImage: ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } : { - name: socialDbSnapshot.name, - description: socialDbSnapshot.description, - publicGoodReason: socialDbSnapshot.plPublicGoodReason, - teamMembers: socialDbSnapshot.plTeam ? JSON.parse(socialDbSnapshot.plTeam) : undefined, + name: socialProfileSnapshot.name, + description: socialProfileSnapshot.description, + publicGoodReason: socialProfileSnapshot.plPublicGoodReason, + + teamMembers: socialProfileSnapshot.plTeam + ? JSON.parse(socialProfileSnapshot.plTeam) + : undefined, - categories: socialDbSnapshot.plCategories - ? JSON.parse(socialDbSnapshot.plCategories) + categories: socialProfileSnapshot.plCategories + ? JSON.parse(socialProfileSnapshot.plCategories) : undefined, - github: socialDbSnapshot.plGithubRepos - ? JSON.parse(socialDbSnapshot.plGithubRepos) + github: socialProfileSnapshot.plGithubRepos + ? JSON.parse(socialProfileSnapshot.plGithubRepos) : undefined, backgroundImage: backgroundSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, profileImage: avatarSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, }, - [avatarSrc, backgroundSrc, socialDbSnapshot], + [avatarSrc, backgroundSrc, socialProfileSnapshot], ); - const onSuccess = useCallback(() => { - toast({ title: "Success!", description: "Project updated successfully" }); - }, [toast]); + const submitButtonLabel = useMemo(() => { + switch (mode) { + case "register": { + return isDaoRepresentative ? "Add proposal to create project" : "Create new project"; + } - const onFailure = useCallback( - (errorMessage: string) => toast({ title: "Error", description: errorMessage }), - [toast], - ); + case "update": { + return isDaoRepresentative ? "Add proposal to update project" : "Update your project"; + } + } + }, [isDaoRepresentative, mode]); const { form, - values, isDisabled, - isSubmitting, + teamMembersAccountGroup, onSubmit, updateCategories, updateBackgroundImage, updateProfileImage, + addRepository, updateRepositories, updateTeamMembers, - addRepository, - errors, } = useProfileSetupForm({ + mode, accountId, + isDaoRepresentative, + socialProfileSnapshot, defaultValues, onSuccess, onFailure, - mode, }); + const values = form.watch(); + + console.log({ defaultValues }); + console.log("Form values:", values); + console.log("Errors:", form.formState.errors, "isValid", form.formState.isValid); + useEffect(() => { // Set initial focus to name input. form.setFocus("name"); @@ -124,18 +143,7 @@ export const ProfileSetupForm: React.FC = ({ accountId, m [updateRepositories], ); - console.log({ defaultValues }); - - const getProjectEditorText = () => { - if (socialDbSnapshot) { - return values.isDao ? "Add proposal to update project" : "Update your project"; - } - - return values.isDao ? "Add proposal to create project" : "Create new project"; - }; - - const projectEditorText = getProjectEditorText(); - + // TODO: Handle DAO representative case in a separate ticket after the initial release // // DAO Status - In Progress // if ( // values.isDao && @@ -145,14 +153,11 @@ export const ProfileSetupForm: React.FC = ({ accountId, m // return ; // } - console.log("Form values:", values); - - console.log(form.formState.errors, form.formState.isValid); - return (
+ = ({ accountId, m - + - - setAddTeamModalOpen(false)} - onMembersChange={onTeamMembersChange} - /> - = ({ accountId, m }} /> - { @@ -199,7 +195,7 @@ export const ProfileSetupForm: React.FC = ({ accountId, m /> @@ -214,7 +210,6 @@ export const ProfileSetupForm: React.FC = ({ accountId, m label="Project name *" inputProps={{ placeholder: "Enter project name", - error: errors.name?.message, ...field, }} /> @@ -223,7 +218,7 @@ export const ProfileSetupForm: React.FC = ({ accountId, m @@ -236,40 +231,42 @@ export const ProfileSetupForm: React.FC = ({ accountId, m showHint label="Describe your project *" placeholder="Type description" - error={errors.description?.message} field={field} currentText={values.description} /> )} /> - <> - {values.categories?.includes("Public Good") ? ( - ( - - )} - /> - ) : null} - + + {values.categories?.includes("Public Good") ? ( + ( + + )} + /> + ) : null} + - + - { setEditFundingIndex(fundingIndex); setAddFundingModalOpen(true); @@ -277,39 +274,36 @@ export const ProfileSetupForm: React.FC = ({ accountId, m />
- +
{/* */} - +
- +
+ - + -
+
diff --git a/src/features/profile-setup/components/FundingSourceTable.tsx b/src/features/profile-setup/components/funding-sources.tsx similarity index 87% rename from src/features/profile-setup/components/FundingSourceTable.tsx rename to src/features/profile-setup/components/funding-sources.tsx index 9261748f..6ff2a2ab 100644 --- a/src/features/profile-setup/components/FundingSourceTable.tsx +++ b/src/features/profile-setup/components/funding-sources.tsx @@ -4,7 +4,8 @@ import { styled } from "styled-components"; import Delete from "@/common/ui/svg/Delete"; import Edit from "@/common/ui/svg/Edit"; -import { dispatch, useGlobalStoreSelector } from "@/store"; + +import type { ProfileSetupInputs } from "../models/types"; // TODO: refactor by breaking into TailwindCSS classes export const Table = styled.div` @@ -96,22 +97,20 @@ export const Table = styled.div` } `; -type Props = { +export type ProfileSetupFundingSourcesTableProps = { + values: ProfileSetupInputs["fundingSources"]; onEditClick: (fundingIndex: number) => void; }; -const FundingSourceTable = ({ onEditClick }: Props) => { - const fundingSources = useGlobalStoreSelector((state) => state.projectEditor.fundingSources); - +export const ProfileSetupFundingSourcesTable: React.FC = ({ + values, + onEditClick, +}) => { const onDeleteHandler = useCallback((fundingIndex: number) => { - dispatch.projectEditor.removeFundingSource(fundingIndex); + //dispatch.projectEditor.removeFundingSource(fundingIndex); }, []); - if (!fundingSources || fundingSources.length === 0) { - return null; - } - - return ( + return values === undefined || values.length === 0 ? null : (
Funding source
@@ -119,7 +118,8 @@ const FundingSourceTable = ({ onEditClick }: Props) => {
Amount
- {fundingSources.map((funding, index) => ( + + {values.map((funding, index) => (

{funding.investorName}

@@ -153,5 +153,3 @@ const FundingSourceTable = ({ onEditClick }: Props) => {
); }; - -export default FundingSourceTable; diff --git a/src/features/profile-setup/components/SocialLinks.tsx b/src/features/profile-setup/components/linktree-section.tsx similarity index 84% rename from src/features/profile-setup/components/SocialLinks.tsx rename to src/features/profile-setup/components/linktree-section.tsx index 46abaa8b..156087b8 100644 --- a/src/features/profile-setup/components/SocialLinks.tsx +++ b/src/features/profile-setup/components/linktree-section.tsx @@ -2,16 +2,17 @@ import { useCallback, useState } from "react"; import { extractFromUrl } from "@/common/lib"; import { ACCOUNT_PROFILE_URL_PATTERNS } from "@/entities/_shared/account"; -import { dispatch, useGlobalStoreSelector } from "@/store"; import { CustomInput } from "./form-elements"; +import type { ProfileSetupInputs } from "../models/types"; -const SocialLinks = () => { - const twitter = useGlobalStoreSelector((state) => state.projectEditor.twitter); - const telegram = useGlobalStoreSelector((state) => state.projectEditor.telegram); - const github = useGlobalStoreSelector((state) => state.projectEditor.github); - const website = useGlobalStoreSelector((state) => state.projectEditor.website); +export type ProfileSetupLinktreeSectionProps = { + values: Pick; +}; +export const ProfileSetupLinktreeSection: React.FC = ({ + values: { twitter, telegram, github, website }, +}) => { const [twitterValue, setTwitterValue] = useState( twitter?.replace("https://x.com/", "") || "", ); @@ -27,7 +28,7 @@ const SocialLinks = () => { const [websiteValue, setWebsiteValue] = useState(website?.replace("https://", "") || ""); const onChangeHandler = useCallback((socialKey: string, value: string) => { - dispatch.projectEditor.updateSocialLinks({ [socialKey]: value }); + // dispatch.projectEditor.updateSocialLinks({ [socialKey]: value }); }, []); return ( @@ -102,5 +103,3 @@ const SocialLinks = () => { ); }; - -export default SocialLinks; diff --git a/src/features/profile-setup/components/repositories-section.tsx b/src/features/profile-setup/components/repositories-section.tsx new file mode 100644 index 00000000..dc1b2d1a --- /dev/null +++ b/src/features/profile-setup/components/repositories-section.tsx @@ -0,0 +1,75 @@ +import { ChangeEvent, useState } from "react"; + +import { extractFromUrl } from "@/common/lib"; +import { ACCOUNT_PROFILE_URL_PATTERNS } from "@/entities/_shared/account"; + +import { CustomInput } from "./form-elements"; +import type { ProfileSetupInputs } from "../models/types"; + +const Repo = ({ + repo, + index, + onChangeHandler, +}: { + repo: string; + index: number; + onChangeHandler: (repoIndex: number, value: string) => void; +}) => { + const [fieldValue, setValue] = useState(repo?.replace("https://github.com/", "") || ""); + + const onChange = (e: ChangeEvent) => { + setValue(extractFromUrl(e.target.value, ACCOUNT_PROFILE_URL_PATTERNS.github) || ""); + }; + + return ( + { + onChangeHandler(index, fieldValue ? `https://github.com/${fieldValue}` : ""); + }, + }} + /> + ); +}; + +export type ProfileSetupRepositoriesSectionProps = { + values: ProfileSetupInputs["githubRepositories"]; + onChange?: (repositories: string[]) => void; +}; + +export const ProfileSetupRepositoriesSection: React.FC = ({ + values, + onChange, +}) => { + // const [repos, setRepos] = useState(repositories.length > 0 ? repositories : [""]); + + // useEffect(() => { + // setRepos(repositories); + // }, [repositories]); + + // const onChangeHandler = useCallback( + // (repoIndex: number, value: string) => { + // const updatedState = [...repositories]; + // updatedState[repoIndex] = value; + // setRepos(updatedState); + // dispatch.projectEditor.updateRepositories(updatedState); + // }, + // [repositories], + // ); + + // useEffect(() => { + // if (onChange) { + // onChange(repos); + // } + // }, [repos, onChange]); + + return values?.map((repo, index) => ( + {}} /> + )); +}; diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 14d3fc6d..25528e0b 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -2,33 +2,33 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; +import { objOf } from "remeda"; import { ZodError } from "zod"; import type { ByAccountId } from "@/common/types"; -import { useSession } from "@/entities/_shared/session"; +import type { AccountGroupItem } from "@/entities/_shared/account"; import { type ProfileSaveInputs, save } from "../models/effects"; import { addFundingSourceSchema, profileSetupSchema } from "../models/schemas"; import { AddFundingSourceInputs, ProfileSetupInputs } from "../models/types"; export type ProfileSetupFormParams = ByAccountId & - Pick & { + Pick & { + defaultValues?: Partial; onSuccess: () => void; onFailure: (errorMessage: string) => void; - defaultValues?: Partial; }; export const useProfileSetupForm = ({ - accountId, mode, + accountId, + isDaoRepresentative, + socialProfileSnapshot, + defaultValues, onSuccess, onFailure, - defaultValues, }: ProfileSetupFormParams) => { - const [submitting, setSubmitting] = useState(false); - const [crossFieldErrors, setCrossFieldErrors] = useState>({}); - const { isSignedIn } = useSession(); const self = useForm({ resolver: zodResolver(profileSetupSchema), @@ -41,11 +41,12 @@ export const useProfileSetupForm = ({ }, }); + //? For internal use only! const values = useWatch({ control: self.control }); - const isDisabled = useMemo( - () => !self.formState.isDirty || !self.formState.isValid || self.formState.isSubmitting, - [self.formState.isDirty, self.formState.isSubmitting, self.formState.isValid], + const teamMembersAccountGroup: AccountGroupItem[] = useMemo( + () => values.teamMembers?.map(objOf("accountId")) ?? [], + [values.teamMembers], ); // Cross-field validation @@ -54,7 +55,7 @@ export const useProfileSetupForm = ({ .parseAsync(values) .then(() => setCrossFieldErrors({})) .catch((error: ZodError) => - // TODO: Consider using `setError` in forEach ( in the future ) + // TODO: Consider using `setError` in forEach if there are any troubles with error display setCrossFieldErrors( error?.issues.reduce((errors, { code, message, path }) => { const fieldPath = path.at(0); @@ -66,70 +67,61 @@ export const useProfileSetupForm = ({ ); }, [values]); - const updateBackgroundImage = useCallback( - (url: string) => { - self.setValue("backgroundImage", url, { shouldValidate: true }); - }, + const isDisabled = useMemo( + () => !self.formState.isDirty || !self.formState.isValid || self.formState.isSubmitting, + [self.formState.isDirty, self.formState.isSubmitting, self.formState.isValid], + ); + const updateBackgroundImage = useCallback( + (url: string) => self.setValue("backgroundImage", url, { shouldValidate: true }), [self], ); const updateProfileImage = useCallback( - (url: string) => { - self.setValue("profileImage", url, { shouldValidate: true }); - }, - + (url: string) => self.setValue("profileImage", url, { shouldValidate: true }), [self], ); - // Form update handlers const updateTeamMembers = useCallback( - (members: string[]) => { - self.setValue("teamMembers", members, { shouldValidate: true }); - }, + (members: string[]) => self.setValue("teamMembers", members, { shouldValidate: true }), [self], ); const updateCategories = useCallback( - (categories: string[]) => { - self.setValue("categories", categories, { shouldValidate: true }); - }, + (categories: string[]) => self.setValue("categories", categories, { shouldValidate: true }), [self], ); const updateRepositories = useCallback( - (repos: string[]) => { - self.setValue("githubRepositories", repos, { shouldValidate: true }); - }, + (repos: string[]) => self.setValue("githubRepositories", repos, { shouldValidate: true }), [self], ); - const addRepository = useCallback(() => { - const currentRepos = values.githubRepositories || []; - self.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true }); - }, [self, values.githubRepositories]); + const addRepository = useCallback( + () => + self.setValue("githubRepositories", [...(values.githubRepositories ?? []), ""], { + shouldValidate: true, + }), + + [self, values.githubRepositories], + ); const onSubmit: SubmitHandler = useCallback( (inputs) => { - if (isSignedIn) { - setSubmitting(true); - - // TODO: pass `isDaoRepresentative` to this effect instead of storing `isDao` as a form field - save({ accountId, isDaoRepresentative: false, mode, data: inputs }) - .then((result) => { - if (result.success) { - onSuccess(); - } else { - onFailure(result.error); - } - }) - .catch((error) => { - console.error(error); - }); - } + save({ accountId, isDaoRepresentative, mode, inputs, socialProfileSnapshot }) + .then((result) => { + if (result.success) { + onSuccess(); + } else { + onFailure(result.error); + } + }) + .catch((error) => { + console.error(error); + }); }, - [accountId, isSignedIn, mode, onFailure, onSuccess], + [accountId, isDaoRepresentative, mode, onFailure, onSuccess, socialProfileSnapshot], ); return { @@ -141,16 +133,14 @@ export const useProfileSetupForm = ({ }, }, - errors: self.formState.errors, - values, isDisabled, - isSubmitting: submitting, + teamMembersAccountGroup, updateBackgroundImage, updateCategories, updateProfileImage, + addRepository, updateRepositories, updateTeamMembers, - addRepository, onSubmit: self.handleSubmit(onSubmit), resetForm: self.reset, }; diff --git a/src/features/profile-setup/hooks/useInitProjectState.ts b/src/features/profile-setup/hooks/useInitProjectState.ts deleted file mode 100644 index 824b3cf2..00000000 --- a/src/features/profile-setup/hooks/useInitProjectState.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { useEffect, useState } from "react"; - -import { LISTS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; -import { listsContractClient } from "@/common/contracts/core"; -import { useRouteQuery } from "@/common/lib"; -import { useWallet } from "@/entities/_shared/session"; -import routesPath from "@/pathnames"; -import { dispatch, useGlobalStoreSelector } from "@/store"; - -// TODO: Ditch Redux for this use case entirely! -//! All form state management should be handled by react-hook-form -export const useInitProjectState = () => { - const { checkRegistrationStatus, accountId, checkPreviousProjectDataStatus } = - useGlobalStoreSelector((state) => state.projectEditor); - - const { - query: { projectId: projectIdPathParam, done, transactionHashes, errorMessage }, - } = useRouteQuery(); - - const projectId = - typeof projectIdPathParam === "string" ? projectIdPathParam : projectIdPathParam?.at(0); - - const { - actAsDao: { defaultAddress: daoAddress, toggle: isDao }, - } = useGlobalStoreSelector((state) => state.nav); - - const { wallet, isWalletReady } = useWallet(); - - // Reset statuses - useEffect(() => { - dispatch.projectEditor.RESET(); - - return () => { - dispatch.projectEditor.RESET(); - }; - }, []); - - // Set current accountId to the state - useEffect(() => { - // Project's id - if (isWalletReady) { - if (isDao && daoAddress) { - dispatch.projectEditor.setAccountId(daoAddress); - dispatch.projectEditor.setIsDao(isDao); - dispatch.projectEditor.setDaoAddress(daoAddress); - } else if (wallet?.accountId) { - dispatch.projectEditor.setAccountId(projectId || wallet.accountId); - } - } - }, [accountId, isDao, daoAddress, wallet?.accountId, isWalletReady, projectId]); - - // Set initial loaded project data - // const [initialDataLoaded, setInitialDataLoaded] = useState(false); - useEffect(() => { - if (accountId && checkPreviousProjectDataStatus !== "ready") { - // load data - dispatch.projectEditor.loadProjectData(accountId); - } - }, [accountId, checkPreviousProjectDataStatus]); - - // Looks for error message after failing tx - useEffect(() => { - if (typeof errorMessage === "string") { - dispatch.projectEditor.submissionStatus("pending"); - dispatch.projectEditor.setSubmissionError(decodeURI(errorMessage)); - } - }, [errorMessage]); - - // Looks for success signal - useEffect(() => { - if (done || transactionHashes) { - dispatch.projectEditor.submissionStatus("done"); - dispatch.projectEditor.setSubmissionError(""); - } - }, [done, transactionHashes]); - - // Check if project is registered - useEffect(() => { - if (accountId && checkRegistrationStatus === "pending") { - dispatch.projectEditor.checkRegistrationStatus("fetching"); - - (async () => { - try { - const register = await listsContractClient.getRegistration({ - registrant_id: accountId, - }); - - // If register found, set that it's registered already - dispatch.projectEditor.isRegistered(!!register); - - // Auto set the project to DONE status if it's already registered & this is create project page - if (register && location.pathname.includes(routesPath.REGISTER)) { - dispatch.projectEditor.submissionStatus("done"); - dispatch.projectEditor.setSubmissionError(""); - } - - dispatch.projectEditor.checkRegistrationStatus("ready"); - } catch (_) { - dispatch.projectEditor.checkRegistrationStatus("pending"); - } - })(); - } - }, [accountId, checkRegistrationStatus]); - - // Reset check registration every time the "isDao" flag is changed - const [previousDaoFlag] = useState(isDao); - - useEffect(() => { - if (previousDaoFlag !== isDao && daoAddress) { - window.location.reload(); - - // dispatch.projectEditor.submissionStatus("pending"); - // dispatch.projectEditor.checkRegistrationStatus("ready"); - // dispatch.projectEditor.checkPreviousProjectDataStatus("ready"); - // setInitialDataLoaded(false); - // setPreviousDaoFlag(isDao); - } - }, [isDao, previousDaoFlag, daoAddress]); - - // Get DAO proposals - Current DAO Proposal status - useEffect(() => { - const checkDao = isDao && daoAddress; - - (async () => { - const proposals = checkDao - ? await naxiosInstance - .contractApi({ contractId: daoAddress }) - .view("get_proposals", { args: { from_index: 0, limit: 1000 } }) - : null; - - const proposal = proposals - ? proposals.find( - (proposal) => - proposal.kind.FunctionCall?.receiver_id === LISTS_CONTRACT_ACCOUNT_ID && - proposal.kind.FunctionCall?.actions[0]?.method_name === "register_batch", - ) - : null; - - // Set proposal - dispatch.projectEditor.setDaoProjectProposal(proposal || null); - })(); - }, [isDao, daoAddress]); -}; diff --git a/src/features/profile-setup/index.ts b/src/features/profile-setup/index.ts index ac1df85d..056058eb 100644 --- a/src/features/profile-setup/index.ts +++ b/src/features/profile-setup/index.ts @@ -1,3 +1 @@ export * from "./components/form"; -export * from "./hooks/useInitProjectState"; -export { projectEditorModel, projectEditorModelKey } from "./models"; diff --git a/src/features/profile-setup/models/index.ts b/src/features/profile-setup/models/deprecated.ts similarity index 98% rename from src/features/profile-setup/models/index.ts rename to src/features/profile-setup/models/deprecated.ts index a596f63d..9f360a5e 100644 --- a/src/features/profile-setup/models/index.ts +++ b/src/features/profile-setup/models/deprecated.ts @@ -1,3 +1,7 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +// @ts-nocheck +// TODO!: ONLY FOR THE REFERENCE, DO NOT IMPORT ANYTHING AND REMOVE THE MODULE BEFORE RELEASE + import { createModel } from "@rematch/core"; import { prop } from "remeda"; diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index fb1df3c1..ef6b17b8 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -1,9 +1,4 @@ -import { - Transaction, - buildTransaction, - calculateDepositByDataSize, - validateNearAddress, -} from "@wpdas/naxios"; +import { Transaction, buildTransaction, calculateDepositByDataSize } from "@wpdas/naxios"; import { Big } from "big.js"; import { parseNearAmount } from "near-api-js/lib/utils/format"; @@ -22,25 +17,25 @@ import type { ByAccountId } from "@/common/types"; import type { ProfileSetupMode } from "../types"; import type { ProfileSetupInputs } from "./types"; -import { profileSetupFormInputsToSocialDbProfileUpdate } from "../utils/normalization"; - -const getSocialData = async (accountId: string) => { - try { - const socialData = await socialDbContractClient.getSocialProfile({ accountId }); - return socialData; - } catch (e) { - console.error(e); - return null; - } -}; +import { profileSetupInputsToSocialDbFormat } from "../utils/normalization"; export type ProfileSaveInputs = ByAccountId & { isDaoRepresentative: boolean; mode: ProfileSetupMode; - data: ProfileSetupInputs; + inputs: ProfileSetupInputs; + socialProfileSnapshot: NEARSocialUserProfile | null; }; -export const save = async ({ isDaoRepresentative, accountId, mode, data }: ProfileSaveInputs) => { +export const save = async ({ + isDaoRepresentative, + accountId, + mode, + inputs, + socialProfileSnapshot, +}: ProfileSaveInputs) => { + // TODO: Should be passed as a separate parameter + //! ( DAO Registration ticket, only AFTER wallet integration revamp! ) + const daoAccountId = accountId; const daoPolicy = isDaoRepresentative ? await getDaoPolicy(accountId) : null; const daoProposalDescription = @@ -48,24 +43,17 @@ export const save = async ({ isDaoRepresentative, accountId, mode, data }: Profi ? "Create project on POTLOCK (2 steps: Register information on NEAR Social and register on POTLOCK)" : "Update project on POTLOCK (via NEAR Social)"; - // Validate DAO Address - if (isDaoRepresentative && !validateNearAddress(data.daoAddress || "")) { - return { success: false, error: "DAO: Invalid NEAR account Id" }; - } - - const socialDbUpdateParams = profileSetupFormInputsToSocialDbProfileUpdate(data); - - // If there is an existing social data, make the diff between then - const existingSocialData = await getSocialData(accountId); + const formattedInputs = profileSetupInputsToSocialDbFormat(inputs); - const nearSocialProfileDiff: NEARSocialUserProfile = existingSocialData - ? deepObjectDiff(existingSocialData, socialDbUpdateParams) - : socialDbUpdateParams; + //? Derive diff from the preexisting social profile + const socialDbProfileUpdate: NEARSocialUserProfile = socialProfileSnapshot + ? deepObjectDiff(socialProfileSnapshot, formattedInputs) + : formattedInputs; const socialArgs = { data: { [accountId]: { - profile: nearSocialProfileDiff, + profile: socialDbProfileUpdate, /** *? Auto Follow and Star Potlock @@ -139,7 +127,7 @@ export const save = async ({ isDaoRepresentative, accountId, mode, data }: Profi }; return { - receiverId: data.daoAddress, + receiverId: daoAccountId, method: "add_proposal", args: { diff --git a/src/features/profile-setup/models/schemas.ts b/src/features/profile-setup/models/schemas.ts index 27f9102b..d2988fb7 100644 --- a/src/features/profile-setup/models/schemas.ts +++ b/src/features/profile-setup/models/schemas.ts @@ -1,4 +1,6 @@ -import { array, boolean, object, string } from "zod"; +import { array, object, string } from "zod"; + +import { ProjectCategory } from "@/entities/project"; export const addFundingSourceSchema = object({ investorName: string({ @@ -24,34 +26,33 @@ export const addFundingSourceSchema = object({ .max(50, "Must be less than 50 characters"), }); +// TODO: Add cross-field validation to make repositories required +//! if one of the project categories is `ProjectCategory["Open Source"]` export const profileSetupSchema = object({ name: string() .min(3, "Must be at least 3 characters long") .max(100, "Must be less than 100 characters long"), - isDao: boolean().default(false), - daoAddress: string().optional(), - backgroundImage: string().min(3).optional(), - profileImage: string().min(3), - teamMembers: array(string()), - categories: array(string()).min(1), - description: string() .min(20, "Must contain at least 20 characters") .max(1500, "Must be less than 1500 characters long"), + backgroundImage: string().min(3).optional(), + profileImage: string().min(3), + website: string().optional(), + twitter: string().optional(), + telegram: string().optional(), + github: string().optional(), + + teamMembers: array(string()).optional(), + categories: array(string()).min(1), + publicGoodReason: string() .min(20, "Must contain at least 20 characters") .max(500, "Must be less 500 characters long") .optional(), smartContracts: array(array(string())).optional(), - fundingSources: array(addFundingSourceSchema).optional(), - githubRepositories: array(string()).optional(), - website: string().optional(), - twitter: string().optional(), - telegram: string().optional(), - github: string().optional(), }); diff --git a/src/features/profile-setup/utils/normalization.ts b/src/features/profile-setup/utils/normalization.ts index 6bb3fc1a..5a20bad8 100644 --- a/src/features/profile-setup/utils/normalization.ts +++ b/src/features/profile-setup/utils/normalization.ts @@ -4,7 +4,7 @@ import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } from "@/entities/_shared/ import type { ProfileSetupInputs } from "../models/types"; -export const profileSetupFormInputsToSocialDbProfileUpdate = (inputs: ProfileSetupInputs) => ({ +export const profileSetupInputsToSocialDbFormat = (inputs: ProfileSetupInputs) => ({ /** *? Standard NEAR Social profile details */ @@ -25,5 +25,5 @@ export const profileSetupFormInputsToSocialDbProfileUpdate = (inputs: ProfileSet plPublicGoodReason: inputs.publicGoodReason, plSmartContracts: inputs.smartContracts ? JSON.stringify(inputs.smartContracts) : undefined, - plTeam: JSON.stringify(inputs.teamMembers), + plTeam: (inputs?.teamMembers ?? []).length > 0 ? JSON.stringify(inputs.teamMembers) : undefined, }); diff --git a/src/features/profile-setup/components/ErrorModal.tsx b/src/layout/pot/_deprecated/ErrorModal.tsx similarity index 82% rename from src/features/profile-setup/components/ErrorModal.tsx rename to src/layout/pot/_deprecated/ErrorModal.tsx index dca01af5..f8aa04a4 100644 --- a/src/features/profile-setup/components/ErrorModal.tsx +++ b/src/layout/pot/_deprecated/ErrorModal.tsx @@ -1,5 +1,4 @@ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@/common/ui/components"; -import { dispatch } from "@/store"; type Props = { open?: boolean; @@ -7,11 +6,11 @@ type Props = { errorMessage?: string; }; +/** + * @deprecated Use toasts instead + */ export const ErrorModal = ({ open, onCloseClick, errorMessage }: Props) => { const closeHandler = () => { - dispatch.projectEditor.submissionStatus("pending"); - dispatch.projectEditor.setSubmissionError(""); - if (onCloseClick) { onCloseClick(); } @@ -35,5 +34,3 @@ export const ErrorModal = ({ open, onCloseClick, errorMessage }: Props) => { ); }; - -export default ErrorModal; diff --git a/src/features/profile-setup/components/SuccessModal.tsx b/src/layout/pot/_deprecated/SuccessModal.tsx similarity index 85% rename from src/features/profile-setup/components/SuccessModal.tsx rename to src/layout/pot/_deprecated/SuccessModal.tsx index 12a1fd0f..21ad555a 100644 --- a/src/features/profile-setup/components/SuccessModal.tsx +++ b/src/layout/pot/_deprecated/SuccessModal.tsx @@ -1,5 +1,4 @@ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from "@/common/ui/components"; -import { dispatch } from "@/store"; type Props = { open?: boolean; @@ -7,11 +6,11 @@ type Props = { successMessage?: string; }; +/** + * @deprecated Use toast instead! + */ export const SuccessModal = ({ open, onCloseClick, successMessage }: Props) => { const closeHandler = () => { - dispatch.projectEditor.submissionStatus("pending"); - dispatch.projectEditor.setSubmissionError(""); - if (onCloseClick) { onCloseClick(); } diff --git a/src/layout/pot/components/layout.tsx b/src/layout/pot/components/layout.tsx index 75585880..4b8e30e2 100644 --- a/src/layout/pot/components/layout.tsx +++ b/src/layout/pot/components/layout.tsx @@ -6,15 +6,14 @@ import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; import { PageWithBanner } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useSession } from "@/entities/_shared/session"; -import { ChallengeModal, usePotAuthorization } from "@/entities/pot"; +import { ChallengeModal } from "@/entities/pot"; import { DonationSybilWarning } from "@/features/donation"; import { MatchingPoolContributionModal } from "@/features/matching-pool-contribution"; import { PotApplicationModal } from "@/features/pot-application"; -import { ErrorModal } from "@/features/profile-setup/components/ErrorModal"; -import { SuccessModal } from "@/features/profile-setup/components/SuccessModal"; +import { SuccessModal } from "@/layout/pot/_deprecated/SuccessModal"; import { PotLayoutHero } from "./layout-hero"; +import { ErrorModal } from "../_deprecated/ErrorModal"; import { usePotLayoutTabNavigation } from "../hooks/tab-navigation"; export type PotLayoutProps = { diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx index 6df7b2fe..4b63b51e 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/edit.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useRouter } from "next/router"; @@ -6,20 +6,25 @@ import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { PageWithBanner } from "@/common/ui/components"; import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; +import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; import { useSession } from "@/entities/_shared/session"; import { ProfileSetupForm } from "@/features/profile-setup"; import { rootPathnames } from "@/pathnames"; export default function EditProjectPage() { - const router = useRouter(); const viewer = useSession(); + const router = useRouter(); + const { toast } = useToast(); - const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = - indexer.useAccountListRegistrations({ - enabled: viewer.isSignedIn, - accountId: viewer.accountId ?? "noop", - }); + const { + isLoading: isAccountListRegistrationDataLoading, + data: listRegistrations, + mutate: refetchListRegistrations, + } = indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); const hasRegistrationSubmitted = useMemo( () => @@ -31,6 +36,16 @@ export default function EditProjectPage() { [isAccountListRegistrationDataLoading, listRegistrations], ); + const onSuccess = useCallback(() => { + toast({ title: "Success!", description: "You have successfully updated your profile." }); + setTimeout(refetchListRegistrations, 3000); + }, [refetchListRegistrations, toast]); + + const onFailure = useCallback( + (errorMessage: string) => toast({ title: "Error", description: errorMessage }), + [toast], + ); + useEffect(() => { if (viewer.isSignedIn && !isAccountListRegistrationDataLoading && !hasRegistrationSubmitted) { router.push(rootPathnames.REGISTER); @@ -65,7 +80,12 @@ export default function EditProjectPage() { ) : ( <> {viewer.isSignedIn ? ( - + ) : ( )} diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 7c705e93..8b3cf2d8 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useRouter } from "next/router"; @@ -6,20 +6,25 @@ import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { PageWithBanner } from "@/common/ui/components"; import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; +import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; import { useSession } from "@/entities/_shared/session"; import { ProfileSetupForm } from "@/features/profile-setup"; import { rootPathnames } from "@/pathnames"; export default function RegisterPage() { - const router = useRouter(); const viewer = useSession(); + const router = useRouter(); + const { toast } = useToast(); - const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = - indexer.useAccountListRegistrations({ - enabled: viewer.isSignedIn, - accountId: viewer.accountId ?? "noop", - }); + const { + isLoading: isAccountListRegistrationDataLoading, + data: listRegistrations, + mutate: refetchListRegistrations, + } = indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); const hasRegistrationSubmitted = useMemo( () => @@ -31,6 +36,16 @@ export default function RegisterPage() { [isAccountListRegistrationDataLoading, listRegistrations], ); + const onSuccess = useCallback(() => { + toast({ title: "Success!", description: "You have successfully submitted your registration." }); + setTimeout(refetchListRegistrations, 3000); + }, [refetchListRegistrations, toast]); + + const onFailure = useCallback( + (errorMessage: string) => toast({ title: "Error", description: errorMessage }), + [toast], + ); + useEffect(() => { if (viewer.isSignedIn && !isAccountListRegistrationDataLoading && hasRegistrationSubmitted) { router.push(`${rootPathnames.PROFILE}/${viewer.accountId}`); @@ -66,7 +81,12 @@ export default function RegisterPage() { ) : ( <> {viewer.isSignedIn ? ( - + ) : ( )} diff --git a/src/store/models.ts b/src/store/models.ts index f6d32e91..9e320fe6 100644 --- a/src/store/models.ts +++ b/src/store/models.ts @@ -6,7 +6,6 @@ import { campaignEditorModel } from "@/entities/campaign/models"; import { listEditorModel } from "@/entities/list"; import { donationModel, donationModelKey } from "@/features/donation"; import { potConfigurationModel, potConfigurationModelKey } from "@/features/pot-configuration"; -import { projectEditorModel, projectEditorModelKey } from "@/features/profile-setup"; import { navModel } from "./nav-model"; @@ -59,7 +58,6 @@ export interface AppModel extends Models { [potConfigurationModelKey]: typeof potConfigurationModel; listEditor: typeof listEditorModel; campaignEditor: typeof campaignEditorModel; - [projectEditorModelKey]: typeof projectEditorModel; } export const models: AppModel = { @@ -70,5 +68,4 @@ export const models: AppModel = { listEditor: listEditorModel, campaignEditor: campaignEditorModel, [potConfigurationModelKey]: potConfigurationModel, - [projectEditorModelKey]: projectEditorModel, }; From 9f182a8ec1a5b3bc04297ecf5ff77f1eb8d31e3c Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 06:02:46 +0000 Subject: [PATCH 37/84] wip --- .../components/AddFundingSourceModal.tsx | 29 +- .../profile-setup/components/form.tsx | 292 +++++++++--------- 2 files changed, 164 insertions(+), 157 deletions(-) diff --git a/src/features/profile-setup/components/AddFundingSourceModal.tsx b/src/features/profile-setup/components/AddFundingSourceModal.tsx index c18bc8bc..f1d1d9cf 100644 --- a/src/features/profile-setup/components/AddFundingSourceModal.tsx +++ b/src/features/profile-setup/components/AddFundingSourceModal.tsx @@ -9,19 +9,24 @@ import { Form, FormField, } from "@/common/ui/components"; -import { dispatch, useGlobalStoreSelector } from "@/store"; import { CustomInput, CustomTextForm } from "./form-elements"; import { useAddFundingSourceForm } from "../hooks/forms"; -import { AddFundingSourceInputs } from "../models/types"; +import { AddFundingSourceInputs, type ProfileSetupInputs } from "../models/types"; -type Props = { +export type ProfileSetupFundingSourceModalProps = { + data: ProfileSetupInputs["fundingSources"]; open?: boolean; onCloseClick?: () => void; editFundingIndex?: number; }; -const AddFundingSourceModal = ({ open, onCloseClick, editFundingIndex }: Props) => { +export const ProfileSetupFundingSourceModal: React.FC = ({ + data: fundingSources = [], + open, + onCloseClick, + editFundingIndex, +}) => { const { form, errors } = useAddFundingSourceForm({ defaultValues: { description: "", @@ -37,10 +42,6 @@ const AddFundingSourceModal = ({ open, onCloseClick, editFundingIndex }: Props) }, }); - const fundingSources = useGlobalStoreSelector( - (state) => state.projectEditor.fundingSources || [], - ); - const isEdit = editFundingIndex !== undefined; const resetForm = useCallback(() => { @@ -49,7 +50,7 @@ const AddFundingSourceModal = ({ open, onCloseClick, editFundingIndex }: Props) const onSubmitFundingSourceHandler = useCallback( (data: AddFundingSourceInputs) => { - dispatch.projectEditor.addFundingSource(data); + // dispatch.projectEditor.addFundingSource(data); if (onCloseClick) { onCloseClick(); @@ -63,10 +64,10 @@ const AddFundingSourceModal = ({ open, onCloseClick, editFundingIndex }: Props) const onSubmitEditedFundingSourceHandler = useCallback( (data: AddFundingSourceInputs) => { if (isEdit && editFundingIndex !== undefined) { - dispatch.projectEditor.updateFundingSource({ - fundingSourceData: data, - index: editFundingIndex, - }); + // dispatch.projectEditor.updateFundingSource({ + // fundingSourceData: data, + // index: editFundingIndex, + // }); resetForm(); } @@ -196,5 +197,3 @@ const AddFundingSourceModal = ({ open, onCloseClick, editFundingIndex }: Props) ); }; - -export default AddFundingSourceModal; diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index a70c03ec..fe440e18 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -14,7 +14,7 @@ import { } from "@/entities/_shared/account"; import { rootPathnames } from "@/pathnames"; -import AddFundingSourceModal from "./AddFundingSourceModal"; +import { ProfileSetupFundingSourceModal } from "./AddFundingSourceModal"; import { ProfileSetupSmartContractModal } from "./contracts-modal"; import { ProfileSetupSmartContractsSection } from "./contracts-section"; import { @@ -154,169 +154,177 @@ export const ProfileSetupForm: React.FC = ({ // } return ( - -
- - - - - - - - - - - { - setAddFundingModalOpen(false); - setEditFundingIndex(undefined); - }} - /> - - { - setEditContractIndex(undefined); - }} - /> - - - - - ( - - )} + <> + { + setAddFundingModalOpen(false); + setEditFundingIndex(undefined); + }} + /> + + { + setEditContractIndex(undefined); + }} + /> + + +
+ + + - - - - - ( - + + - )} + + + + - {values.categories?.includes("Public Good") ? ( + + ( + + )} + /> + + + + + ( )} /> - ) : null} - - - - - - - - - - { - setEditFundingIndex(fundingIndex); - setAddFundingModalOpen(true); - }} - /> - -
- -
+ {values.categories?.includes("Public Good") ? ( + ( + + )} + /> + ) : null} +
- {/* */} + - - - + + + -
- -
+ - + { + setEditFundingIndex(fundingIndex); + setAddFundingModalOpen(true); + }} + /> - - - +
+ +
-
- + {/* */} - + + + + +
+ +
+ + + + + + + +
+ + + +
-
- + + ); }; From 0519f5771546306e4242fef3525a3bfb59f9527e Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 07:46:41 +0000 Subject: [PATCH 38/84] wip --- .../profile-setup/components/form.tsx | 263 +++++++++--------- src/features/profile-setup/hooks/forms.ts | 9 +- 2 files changed, 138 insertions(+), 134 deletions(-) diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index fe440e18..8c98e5ec 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -1,10 +1,9 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/router"; -import { Form } from "react-hook-form"; import { pick } from "remeda"; -import { Button, FormField } from "@/common/ui/components"; +import { Button, Form, FormField } from "@/common/ui/components"; import PlusIcon from "@/common/ui/svg/PlusIcon"; import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, @@ -56,13 +55,16 @@ export const ProfileSetupForm: React.FC = ({ backgroundSrc, } = useAccountSocialProfile({ accountId }); - const defaultValues = useMemo>( + const defaultValues: Partial = useMemo( () => socialProfileSnapshot === null ? { profileImage: ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } : { name: socialProfileSnapshot.name, description: socialProfileSnapshot.description, + backgroundImage: backgroundSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + profileImage: avatarSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + publicGoodReason: socialProfileSnapshot.plPublicGoodReason, teamMembers: socialProfileSnapshot.plTeam @@ -73,12 +75,9 @@ export const ProfileSetupForm: React.FC = ({ ? JSON.parse(socialProfileSnapshot.plCategories) : undefined, - github: socialProfileSnapshot.plGithubRepos + githubRepositories: socialProfileSnapshot.plGithubRepos ? JSON.parse(socialProfileSnapshot.plGithubRepos) : undefined, - - backgroundImage: backgroundSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, - profileImage: avatarSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, }, [avatarSrc, backgroundSrc, socialProfileSnapshot], @@ -99,6 +98,7 @@ export const ProfileSetupForm: React.FC = ({ const { form, isDisabled, + //values, teamMembersAccountGroup, onSubmit, updateCategories, @@ -175,155 +175,152 @@ export const ProfileSetupForm: React.FC = ({ />
-
- - - - - - - - - - - - - - ( - - )} + +
+ + + - - - - - ( - + + - )} + + + + - {values.categories?.includes("Public Good") ? ( + ( + + )} + /> + + + + + + ( )} /> - ) : null} - - + {values.categories?.includes("Public Good") ? ( + ( + + )} + /> + ) : null} + - - - - - - - { - setEditFundingIndex(fundingIndex); - setAddFundingModalOpen(true); - }} - /> - -
- -
+ - {/* */} + + + - - - + -
- -
+ { + setEditFundingIndex(fundingIndex); + setAddFundingModalOpen(true); + }} + /> - +
+ +
- - - + {/* */} -
- - - + + + + +
+ +
+ + + + + + + +
+ + + +
-
+ ); diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 25528e0b..4fc7c0d0 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -14,7 +14,7 @@ import { AddFundingSourceInputs, ProfileSetupInputs } from "../models/types"; export type ProfileSetupFormParams = ByAccountId & Pick & { - defaultValues?: Partial; + defaultValues: Partial; onSuccess: () => void; onFailure: (errorMessage: string) => void; }; @@ -72,6 +72,12 @@ export const useProfileSetupForm = ({ [self.formState.isDirty, self.formState.isSubmitting, self.formState.isValid], ); + console.log({ + isDirty: self.formState.isDirty, + isValid: self.formState.isValid, + isSubmitting: self.formState.isSubmitting, + }); + const updateBackgroundImage = useCallback( (url: string) => self.setValue("backgroundImage", url, { shouldValidate: true }), [self], @@ -143,6 +149,7 @@ export const useProfileSetupForm = ({ updateTeamMembers, onSubmit: self.handleSubmit(onSubmit), resetForm: self.reset, + values, }; }; From 80d3aadfdc3688c4a77dec269ffc1bdda886ce93 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 08:10:20 +0000 Subject: [PATCH 39/84] wip --- .../profile-setup/components/form.tsx | 5 +++-- src/features/profile-setup/hooks/forms.ts | 15 ++++++++------- src/pages/profile/edit.tsx | 19 ++++++++++++------- src/pages/register.tsx | 19 ++++++++++++------- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index 8c98e5ec..1fc1f477 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -50,6 +50,7 @@ export const ProfileSetupForm: React.FC = ({ const [editContractIndex, setEditContractIndex] = useState(); const { + isLoading: isSocialProfileSnapshotLoading, profile: socialProfileSnapshot = null, avatarSrc, backgroundSrc, @@ -62,8 +63,8 @@ export const ProfileSetupForm: React.FC = ({ : { name: socialProfileSnapshot.name, description: socialProfileSnapshot.description, - backgroundImage: backgroundSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, - profileImage: avatarSrc ?? ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, + backgroundImage: backgroundSrc, + profileImage: avatarSrc, publicGoodReason: socialProfileSnapshot.plPublicGoodReason, diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 4fc7c0d0..cdafd3e8 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -32,12 +32,11 @@ export const useProfileSetupForm = ({ const self = useForm({ resolver: zodResolver(profileSetupSchema), - mode: "onChange", + mode: "all", defaultValues, resetOptions: { - keepDefaultValues: true, - keepDirty: false, + keepDirty: true, }, }); @@ -57,11 +56,12 @@ export const useProfileSetupForm = ({ .catch((error: ZodError) => // TODO: Consider using `setError` in forEach if there are any troubles with error display setCrossFieldErrors( - error?.issues.reduce((errors, { code, message, path }) => { + error?.issues.reduce((schemaErrors, { code, message, path }) => { const fieldPath = path.at(0); + return typeof fieldPath === "string" && code === "custom" - ? { ...errors, [fieldPath]: { message, type: code } } - : errors; + ? { ...schemaErrors, [fieldPath]: { message, type: code } } + : schemaErrors; }, {}), ), ); @@ -133,6 +133,7 @@ export const useProfileSetupForm = ({ return { form: { ...self, + formState: { ...self.formState, errors: { ...self.formState.errors, ...crossFieldErrors }, @@ -149,7 +150,6 @@ export const useProfileSetupForm = ({ updateTeamMembers, onSubmit: self.handleSubmit(onSubmit), resetForm: self.reset, - values, }; }; @@ -192,6 +192,7 @@ export const useAddFundingSourceForm = (options: { return { form: { ...form, + formState: { ...form.formState, errors: form.formState.errors, diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx index 4b63b51e..b662f927 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/edit.tsx @@ -1,10 +1,11 @@ import { useCallback, useEffect, useMemo } from "react"; import { useRouter } from "next/router"; +import { MdOutlineInfo } from "react-icons/md"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { PageWithBanner } from "@/common/ui/components"; +import { Alert, AlertDescription, AlertTitle, PageWithBanner } from "@/common/ui/components"; import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; @@ -75,21 +76,25 @@ export default function EditProjectPage() { - {listRegistrations === undefined ? ( - - ) : ( + {viewer.isSignedIn ? ( <> - {viewer.isSignedIn ? ( + {listRegistrations === undefined ? ( + + ) : ( - ) : ( - )} + ) : ( + + + {"Not logged in"} + {"Please connect your wallet to continue"} + )} ); diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 8b3cf2d8..601e3242 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,10 +1,11 @@ import { useCallback, useEffect, useMemo } from "react"; import { useRouter } from "next/router"; +import { MdOutlineInfo } from "react-icons/md"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { PageWithBanner } from "@/common/ui/components"; +import { Alert, AlertDescription, AlertTitle, PageWithBanner } from "@/common/ui/components"; import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; @@ -76,21 +77,25 @@ export default function RegisterPage() { - {listRegistrations === undefined ? ( - - ) : ( + {viewer.isSignedIn ? ( <> - {viewer.isSignedIn ? ( + {listRegistrations === undefined ? ( + + ) : ( - ) : ( - )} + ) : ( + + + {"Not logged in"} + {"Please connect your wallet to continue"} + )} ); From 18859534b40d70747051250f3099d78c600920d0 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 08:13:18 +0000 Subject: [PATCH 40/84] wip --- src/pages/profile/edit.tsx | 2 +- src/pages/register.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx index b662f927..9491a7df 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/edit.tsx @@ -90,7 +90,7 @@ export default function EditProjectPage() { )} ) : ( - + {"Not logged in"} {"Please connect your wallet to continue"} diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 601e3242..9904bf8e 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -91,7 +91,7 @@ export default function RegisterPage() { )} ) : ( - + {"Not logged in"} {"Please connect your wallet to continue"} From 71d85e99d381f55049cabc0f259059b52ab5a387 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 08:19:16 +0000 Subject: [PATCH 41/84] wip --- src/features/profile-setup/components/form.tsx | 2 +- src/pages/profile/edit.tsx | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index 1fc1f477..b60e86a0 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -50,7 +50,7 @@ export const ProfileSetupForm: React.FC = ({ const [editContractIndex, setEditContractIndex] = useState(); const { - isLoading: isSocialProfileSnapshotLoading, + // isLoading: isSocialProfileSnapshotLoading, profile: socialProfileSnapshot = null, avatarSrc, backgroundSrc, diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx index 9491a7df..3d5b9eec 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/edit.tsx @@ -18,14 +18,11 @@ export default function EditProjectPage() { const router = useRouter(); const { toast } = useToast(); - const { - isLoading: isAccountListRegistrationDataLoading, - data: listRegistrations, - mutate: refetchListRegistrations, - } = indexer.useAccountListRegistrations({ - enabled: viewer.isSignedIn, - accountId: viewer.accountId ?? "noop", - }); + const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = + indexer.useAccountListRegistrations({ + enabled: viewer.isSignedIn, + accountId: viewer.accountId ?? "noop", + }); const hasRegistrationSubmitted = useMemo( () => @@ -38,9 +35,9 @@ export default function EditProjectPage() { ); const onSuccess = useCallback(() => { + setTimeout(() => router.push(`${rootPathnames.PROFILE}/${viewer.accountId}`), 3000); toast({ title: "Success!", description: "You have successfully updated your profile." }); - setTimeout(refetchListRegistrations, 3000); - }, [refetchListRegistrations, toast]); + }, [router, toast, viewer.accountId]); const onFailure = useCallback( (errorMessage: string) => toast({ title: "Error", description: errorMessage }), From 8e19ec46a7a3f079c98c9a0b44c4a75436c6c1ca Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 11:33:55 +0000 Subject: [PATCH 42/84] wip --- src/common/contracts/social/client.ts | 3 +- .../_shared/account/hooks/social-profile.ts | 10 ++- .../profile-setup/components/form.tsx | 46 ---------- .../profile-setup/components/image-upload.tsx | 78 +++++++++++------ src/features/profile-setup/hooks/forms.ts | 86 +++++++++++++++---- src/features/profile-setup/models/effects.ts | 2 +- src/pages/profile/edit.tsx | 2 +- 7 files changed, 131 insertions(+), 96 deletions(-) diff --git a/src/common/contracts/social/client.ts b/src/common/contracts/social/client.ts index c84d020e..eba4ea4e 100644 --- a/src/common/contracts/social/client.ts +++ b/src/common/contracts/social/client.ts @@ -1,4 +1,4 @@ -import { StorageCache, buildTransaction } from "@wpdas/naxios"; +import { buildTransaction } from "@wpdas/naxios"; import { SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; import { naxiosInstance } from "@/common/api/near/client"; @@ -9,7 +9,6 @@ import { AccountId } from "@/common/types"; */ const nearSocialDbContractApi = naxiosInstance.contractApi({ contractId: SOCIAL_DB_CONTRACT_ACCOUNT_ID, - cache: new StorageCache({ expirationTime: 5 * 60 }), // 5 minutes }); interface NEARSocialUserProfileInput { diff --git a/src/entities/_shared/account/hooks/social-profile.ts b/src/entities/_shared/account/hooks/social-profile.ts index 7abc18c7..e15ea8e2 100644 --- a/src/entities/_shared/account/hooks/social-profile.ts +++ b/src/entities/_shared/account/hooks/social-profile.ts @@ -12,7 +12,13 @@ export const useAccountSocialProfile = ({ accountId, enabled = true, }: ByAccountId & ConditionalActivation) => { - const { isLoading, data, error } = socialDbContractHooks.useSocialProfile({ enabled, accountId }); + const { + isLoading, + isValidating, + data, + mutate: refetch, + error, + } = socialDbContractHooks.useSocialProfile({ enabled, accountId }); const avatarSrc = useMemo( () => @@ -40,9 +46,11 @@ export const useAccountSocialProfile = ({ return { isLoading, + isValidating, profile: data ?? undefined, avatarSrc, backgroundSrc, + refetch, error, }; }; diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index b60e86a0..eaf15e1d 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -9,7 +9,6 @@ import { ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC, ACCOUNT_PROFILE_LINKTREE_KEYS, AccountGroup, - useAccountSocialProfile, } from "@/entities/_shared/account"; import { rootPathnames } from "@/pathnames"; @@ -29,7 +28,6 @@ import { ProfileSetupLinktreeSection } from "./linktree-section"; import { ProfileSetupRepositoriesSection } from "./repositories-section"; import { LowerBannerContainer, LowerBannerContainerLeft } from "./styles"; import { type ProfileSetupFormParams, useProfileSetupForm } from "../hooks/forms"; -import { ProfileSetupInputs } from "../models/types"; export type ProfileSetupFormProps = Pick< ProfileSetupFormParams, @@ -49,41 +47,6 @@ export const ProfileSetupForm: React.FC = ({ const [editFundingIndex, setEditFundingIndex] = useState(); const [editContractIndex, setEditContractIndex] = useState(); - const { - // isLoading: isSocialProfileSnapshotLoading, - profile: socialProfileSnapshot = null, - avatarSrc, - backgroundSrc, - } = useAccountSocialProfile({ accountId }); - - const defaultValues: Partial = useMemo( - () => - socialProfileSnapshot === null - ? { profileImage: ACCOUNT_PROFILE_COVER_IMAGE_PLACEHOLDER_SRC } - : { - name: socialProfileSnapshot.name, - description: socialProfileSnapshot.description, - backgroundImage: backgroundSrc, - profileImage: avatarSrc, - - publicGoodReason: socialProfileSnapshot.plPublicGoodReason, - - teamMembers: socialProfileSnapshot.plTeam - ? JSON.parse(socialProfileSnapshot.plTeam) - : undefined, - - categories: socialProfileSnapshot.plCategories - ? JSON.parse(socialProfileSnapshot.plCategories) - : undefined, - - githubRepositories: socialProfileSnapshot.plGithubRepos - ? JSON.parse(socialProfileSnapshot.plGithubRepos) - : undefined, - }, - - [avatarSrc, backgroundSrc, socialProfileSnapshot], - ); - const submitButtonLabel = useMemo(() => { switch (mode) { case "register": { @@ -99,7 +62,6 @@ export const ProfileSetupForm: React.FC = ({ const { form, isDisabled, - //values, teamMembersAccountGroup, onSubmit, updateCategories, @@ -112,23 +74,15 @@ export const ProfileSetupForm: React.FC = ({ mode, accountId, isDaoRepresentative, - socialProfileSnapshot, - defaultValues, onSuccess, onFailure, }); const values = form.watch(); - console.log({ defaultValues }); console.log("Form values:", values); console.log("Errors:", form.formState.errors, "isValid", form.formState.isValid); - useEffect(() => { - // Set initial focus to name input. - form.setFocus("name"); - }, [form]); - const onCategoriesChange = useCallback( (categories: string[]) => updateCategories(categories), [updateCategories], diff --git a/src/features/profile-setup/components/image-upload.tsx b/src/features/profile-setup/components/image-upload.tsx index ea0db5f2..29278f96 100644 --- a/src/features/profile-setup/components/image-upload.tsx +++ b/src/features/profile-setup/components/image-upload.tsx @@ -1,22 +1,22 @@ import { useState } from "react"; import Files from "react-files"; +import { LazyLoadImage } from "react-lazy-load-image-component"; import { nearSocialIpfsImageUpload } from "@/common/services/ipfs"; import { Button, Spinner } from "@/common/ui/components"; +import { useToast } from "@/common/ui/hooks"; import CameraIcon from "@/common/ui/svg/CameraIcon"; +import { cn } from "@/common/ui/utils"; import type { ProfileSetupInputs } from "../models/types"; type Status = "ready" | "loading"; const useStatus = (initialStatus: Status = "ready") => { - const [status, setStatus] = useState(initialStatus); + const [current, set] = useState(initialStatus); - return { - status, - setStatus, - }; + return { current, set }; }; export type ProfileSetupImageUploadProps = Pick< @@ -33,26 +33,43 @@ export const ProfileSetupImageUpload: React.FC = ( onBackgroundImageUploaded, onProfileImageUploaded, }) => { + const { toast } = useToast(); const bgImageStatus = useStatus(); const profileImageStatus = useStatus(); const onBgImageChange = async (files?: File[]) => { if (files) { - nearSocialIpfsImageUpload(files).then((url) => { - if (url !== undefined) { - onBackgroundImageUploaded(url); - } - }); + bgImageStatus.set("loading"); + + nearSocialIpfsImageUpload(files) + .then((url) => { + if (url !== undefined) { + onBackgroundImageUploaded(url); + toast({ title: "Background image successfully uploaded" }); + } + }) + .catch((error) => { + console.log(error); + toast({ title: "Image upload error", description: error }); + }) + .finally(() => bgImageStatus.set("ready")); } }; const onAvatarImageChange = async (files?: File[]) => { if (files) { - nearSocialIpfsImageUpload(files).then((url) => { - if (url !== undefined) { - onProfileImageUploaded(url); - } - }); + nearSocialIpfsImageUpload(files) + .then((url) => { + if (url !== undefined) { + onProfileImageUploaded(url); + toast({ title: "Profile image successfully uploaded" }); + } + }) + .catch((error) => { + console.log(error); + toast({ title: "Image upload error", description: error }); + }) + .finally(() => profileImageStatus.set("ready")); } }; @@ -61,19 +78,23 @@ export const ProfileSetupImageUpload: React.FC = ( {/* BackgroundImage */}
{backgroundImage && ( - Profile Background )}
); }; - -export default Team; diff --git a/src/entities/project/index.ts b/src/entities/project/index.ts index 30d12719..775be513 100644 --- a/src/entities/project/index.ts +++ b/src/entities/project/index.ts @@ -3,4 +3,5 @@ export * from "./types"; export * from "./components/ProjectBanner"; export * from "./components/ProjectCard"; export * from "./components/ProjectDiscovery"; +export * from "./components/Team"; export * from "./hooks/lookup"; diff --git a/src/features/profile-setup/hooks/forms.ts b/src/features/profile-setup/hooks/forms.ts index 6e5a750e..af614922 100644 --- a/src/features/profile-setup/hooks/forms.ts +++ b/src/features/profile-setup/hooks/forms.ts @@ -29,7 +29,6 @@ export const useProfileSetupForm = ({ const { isLoading: isSocialProfileSnapshotLoading, - isValidating: isSocialProfileSnapshotRevalidating, profile: socialProfileSnapshot, avatarSrc, backgroundSrc, diff --git a/src/layout/profile/components/ProfileLayout.tsx b/src/layout/profile/components/ProfileLayout.tsx index 2ec6a24a..a54c17b1 100644 --- a/src/layout/profile/components/ProfileLayout.tsx +++ b/src/layout/profile/components/ProfileLayout.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { useRegistration } from "@/common/_deprecated/useRegistration"; +import type { AccountId } from "@/common/types"; import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; import { ProjectBanner } from "@/entities/project"; @@ -71,12 +72,9 @@ type ProfileLayoutTabPanelProps = { }; const Tabs: React.FC = ({ options, selectedTab, onSelect, asLink }) => { - const _selectedTab = selectedTab || options[0].id; - const router = useRouter(); - const { userId: userIdPathParam } = router.query; - - const userId = typeof userIdPathParam === "string" ? userIdPathParam : userIdPathParam?.at(0); + const { accountId } = router.query as { accountId: AccountId }; + const _selectedTab = selectedTab || options[0].id; return (
@@ -88,7 +86,7 @@ const Tabs: React.FC = ({ options, selectedTab, onSe if (asLink) { return ( { @@ -127,12 +125,11 @@ export type ProfileLayoutProps = { }; export const ProfileLayout: React.FC = ({ children }) => { - const router = useRouter(); - const params = router.query as { userId?: string }; - const pathname = router.pathname; + const { query, pathname } = useRouter(); + const { accountId } = query as { accountId: AccountId }; // Load profile data - const { isRegisteredProject } = useRegistration(params.userId || ""); + const { isRegisteredProject } = useRegistration(accountId || ""); const tabs = isRegisteredProject ? tabRoutesProject : tabRoutesProfile; @@ -144,17 +141,13 @@ export const ProfileLayout: React.FC = ({ children }) => { setSelectedTab(tabs.find((tab) => pathname.includes(tab.href)) || tabs[0]); }, [pathname, tabs]); - if (!params.userId) { - return ""; - } - const isProject = isRegisteredProject; return ( - {isProject && } - - + {isProject && } + + { const router = useRouter(); - const { account, block } = useRouter().query; + const { account, block } = router.query; const [post, setPost] = useState<{ accountId: string; diff --git a/src/pages/profile/[userId]/campaigns.tsx b/src/pages/profile/[accountId]/campaigns.tsx similarity index 94% rename from src/pages/profile/[userId]/campaigns.tsx rename to src/pages/profile/[accountId]/campaigns.tsx index 5d6b54c8..6f1a5543 100644 --- a/src/pages/profile/[userId]/campaigns.tsx +++ b/src/pages/profile/[accountId]/campaigns.tsx @@ -10,7 +10,7 @@ import { NoResults } from "./lists"; const ProfileCampaigns = () => { const router = useRouter(); - const { userId: accountId } = router.query as { userId: string }; + const { accountId } = router.query as { accountId: string }; const [campaigns, setCampaigns] = useState([]); useEffect(() => { diff --git a/src/pages/profile/[userId]/donations.tsx b/src/pages/profile/[accountId]/donations.tsx similarity index 92% rename from src/pages/profile/[userId]/donations.tsx rename to src/pages/profile/[accountId]/donations.tsx index a9db6506..80fbea0e 100644 --- a/src/pages/profile/[userId]/donations.tsx +++ b/src/pages/profile/[accountId]/donations.tsx @@ -5,12 +5,13 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; +import type { AccountId } from "@/common/types"; import { FundingTable } from "@/layout/profile/_deprecated/FundingTable"; import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; const DonationsTab = () => { const router = useRouter(); - const { userId: accountId } = router.query as { userId: string }; + const { accountId } = router.query as { accountId: AccountId }; const { data: donationsData } = indexer.useAccountDonationsSent({ accountId }); const hasDonations = donationsData?.results && donationsData.results.length > 0; diff --git a/src/pages/profile/[userId]/feed.tsx b/src/pages/profile/[accountId]/feed.tsx similarity index 88% rename from src/pages/profile/[userId]/feed.tsx rename to src/pages/profile/[accountId]/feed.tsx index ac5da9b0..4e70b190 100644 --- a/src/pages/profile/[userId]/feed.tsx +++ b/src/pages/profile/[accountId]/feed.tsx @@ -3,10 +3,11 @@ import React, { ReactElement, useEffect, useRef, useState } from "react"; import { useRouter } from "next/router"; import InfiniteScrollWrapper from "react-infinite-scroll-component"; -import { walletApi } from "@/common/api/near/client"; import { fetchAccountFeedPosts } from "@/common/api/near-social"; import { IndexPostResultItem } from "@/common/contracts/social"; +import type { AccountId } from "@/common/types"; import { cn } from "@/common/ui/utils"; +import { useSession } from "@/entities/_shared/session"; import { PostCard, PostEditor } from "@/entities/post"; import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; @@ -36,7 +37,8 @@ const NoResults = () => ( export default function ProfileFeedTab() { const router = useRouter(); - const { userId: accountId } = router.query as { userId: string }; + const { accountId } = router.query as { accountId: AccountId }; + const viewer = useSession(); const [posts, setPosts] = useState([]); const [offset, setOffset] = useState(40); const [isLoading, setIsLoading] = useState(false); @@ -45,7 +47,7 @@ export default function ProfileFeedTab() { useEffect(() => { setIsLoading(true); - fetchAccountFeedPosts({ accountId }).then((res: any) => { + fetchAccountFeedPosts({ accountId }).then((res) => { setIsLoading(false); setPosts(res); }); @@ -68,7 +70,7 @@ export default function ProfileFeedTab() { return (
- {accountId === walletApi?.accountId && } + {accountId === viewer.accountId && } isAccountId(accountId), [accountId]); const { profile } = useAccountSocialProfile({ enabled: isAccountIdValid, accountId }); diff --git a/src/pages/profile/[userId]/lists.tsx b/src/pages/profile/[accountId]/lists.tsx similarity index 92% rename from src/pages/profile/[userId]/lists.tsx rename to src/pages/profile/[accountId]/lists.tsx index cab51df3..c41df969 100644 --- a/src/pages/profile/[userId]/lists.tsx +++ b/src/pages/profile/[accountId]/lists.tsx @@ -3,6 +3,7 @@ import { ReactElement, useState } from "react"; import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; +import type { AccountId } from "@/common/types"; import { Label, Switch } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { ListCard, getRandomBackgroundImage } from "@/entities/list"; @@ -35,7 +36,7 @@ export const NoResults = ({ text }: { text: string }) => ( const ProfileLists = () => { const router = useRouter(); const [administratedListsOnly, setAdministratedListsOnly] = useState(false); - const { userId: accountId } = router.query as { userId: string }; + const { accountId } = router.query as { accountId: AccountId }; const { data } = indexer.useLists({ account: accountId, @@ -55,11 +56,11 @@ const ProfileLists = () => {
{data?.results?.length ? (
- {data?.results?.map((item: any) => { + {data?.results?.map((item) => { let background = ""; let backdrop = ""; - if (!item.cover_image) { + if (!item.cover_image_url) { ({ background, backdrop } = getRandomBackgroundImage()); } diff --git a/src/pages/profile/[userId]/pots.tsx b/src/pages/profile/[accountId]/pots.tsx similarity index 93% rename from src/pages/profile/[userId]/pots.tsx rename to src/pages/profile/[accountId]/pots.tsx index d1b50782..156546e9 100644 --- a/src/pages/profile/[userId]/pots.tsx +++ b/src/pages/profile/[accountId]/pots.tsx @@ -3,12 +3,13 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; +import type { AccountId } from "@/common/types"; import { PotCard } from "@/entities/pot"; import { ProfileLayout } from "@/layout/profile/components/ProfileLayout"; export default function ProfilePotsTab() { const router = useRouter(); - const { userId: accountId } = router.query as { userId: string }; + const { accountId } = router.query as { accountId: AccountId }; const { data: paginatedPotApplications, isLoading } = indexer.useAccountPotApplications({ accountId, diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/edit.tsx index b6ffb1e9..5ee94bfd 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/edit.tsx @@ -1,11 +1,18 @@ import { useCallback, useEffect, useMemo } from "react"; +import Link from "next/link"; import { useRouter } from "next/router"; import { MdOutlineInfo } from "react-icons/md"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { Alert, AlertDescription, AlertTitle, PageWithBanner } from "@/common/ui/components"; +import { + Alert, + AlertDescription, + AlertTitle, + Button, + PageWithBanner, +} from "@/common/ui/components"; import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; @@ -35,8 +42,18 @@ export default function EditProjectPage() { ); const onSuccess = useCallback(() => { - // setTimeout(() => router.push(`${rootPathnames.PROFILE}/${viewer.accountId}`), 3000); - toast({ title: "Success!", description: "You have successfully updated your profile." }); + setTimeout(() => router.push(`${rootPathnames.PROFILE}/${viewer.accountId}`), 3000); + + toast({ + title: "Success!", + description: "You have successfully updated your profile.", + + action: ( + + ), + }); }, [router, toast, viewer.accountId]); const onFailure = useCallback( From 66c9e10bbd4e1817015e7604c1a0088db3fb1e3c Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 12:06:18 +0000 Subject: [PATCH 44/84] wip --- src/pages/profile/{ => [accountId]}/edit.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) rename src/pages/profile/{ => [accountId]}/edit.tsx (96%) diff --git a/src/pages/profile/edit.tsx b/src/pages/profile/[accountId]/edit.tsx similarity index 96% rename from src/pages/profile/edit.tsx rename to src/pages/profile/[accountId]/edit.tsx index 5ee94bfd..737b9a5c 100644 --- a/src/pages/profile/edit.tsx +++ b/src/pages/profile/[accountId]/edit.tsx @@ -6,6 +6,7 @@ import { MdOutlineInfo } from "react-icons/md"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import type { AccountId } from "@/common/types"; import { Alert, AlertDescription, @@ -21,8 +22,9 @@ import { ProfileSetupForm } from "@/features/profile-setup"; import { rootPathnames } from "@/pathnames"; export default function EditProjectPage() { - const viewer = useSession(); const router = useRouter(); + // const { accountId } = router.query as { accountId: AccountId }; + const viewer = useSession(); const { toast } = useToast(); const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = From 097e6c2383441c01cba7eba5040e9478f007ce76 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 12:23:31 +0000 Subject: [PATCH 45/84] wip --- .../components/{contracts-modal.tsx => contract-modal.tsx} | 0 src/features/profile-setup/components/form.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/features/profile-setup/components/{contracts-modal.tsx => contract-modal.tsx} (100%) diff --git a/src/features/profile-setup/components/contracts-modal.tsx b/src/features/profile-setup/components/contract-modal.tsx similarity index 100% rename from src/features/profile-setup/components/contracts-modal.tsx rename to src/features/profile-setup/components/contract-modal.tsx diff --git a/src/features/profile-setup/components/form.tsx b/src/features/profile-setup/components/form.tsx index eaf15e1d..00d67999 100644 --- a/src/features/profile-setup/components/form.tsx +++ b/src/features/profile-setup/components/form.tsx @@ -13,7 +13,7 @@ import { import { rootPathnames } from "@/pathnames"; import { ProfileSetupFundingSourceModal } from "./AddFundingSourceModal"; -import { ProfileSetupSmartContractModal } from "./contracts-modal"; +import { ProfileSetupSmartContractModal } from "./contract-modal"; import { ProfileSetupSmartContractsSection } from "./contracts-section"; import { CustomInput, From c93b18850609b6527e00d28e458701c02851fa75 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 14:29:06 +0000 Subject: [PATCH 46/84] wip --- src/features/profile-setup/models/deprecated.ts | 2 +- src/layout/profile/components/ProfileLayoutControls.tsx | 3 ++- src/pathnames.ts | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/features/profile-setup/models/deprecated.ts b/src/features/profile-setup/models/deprecated.ts index 9f360a5e..e86ce0ac 100644 --- a/src/features/profile-setup/models/deprecated.ts +++ b/src/features/profile-setup/models/deprecated.ts @@ -289,7 +289,7 @@ export const projectEditorModel = createModel()({ const data: Partial = {}; // Set the isEdit status - data.isEdit = location.pathname.includes(rootPathnames.EDIT_PROFILE); + //data.isEdit = location.pathname.includes(rootPathnames.EDIT_PROFILE); // Get profile data & profile images const projectProfileData = await fetchSocialImages({ diff --git a/src/layout/profile/components/ProfileLayoutControls.tsx b/src/layout/profile/components/ProfileLayoutControls.tsx index 8abf563e..817d9f3a 100644 --- a/src/layout/profile/components/ProfileLayoutControls.tsx +++ b/src/layout/profile/components/ProfileLayoutControls.tsx @@ -169,9 +169,10 @@ export const ProfileLayoutControls = ({ accountId, isProject }: Props) => { {/* Copy Icon */}
+ {isOwner && (
- + diff --git a/src/pathnames.ts b/src/pathnames.ts index d7710baa..72430e9c 100644 --- a/src/pathnames.ts +++ b/src/pathnames.ts @@ -1,9 +1,11 @@ +import type { AccountId } from "./common/types"; + export const rootPathnames = { CURRENT: "", PROJECTS_LIST: "/", REGISTER: "/register", PROFILE: "/profile", - EDIT_PROFILE: "/profile/edit", + EDIT_PROFILE: (accountId: AccountId) => `/profile/${accountId}/edit`, CART: "/cart", FEED: "/feed", POTS: "/pots", From 6ee17772d1737a6cdc623fcfbaec3d7ce8f0cd47 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Thu, 23 Jan 2025 15:16:15 +0000 Subject: [PATCH 47/84] Remove MyNearWallet support --- src/common/api/near/client.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/api/near/client.ts b/src/common/api/near/client.ts index 47618e72..0dbfbc5d 100644 --- a/src/common/api/near/client.ts +++ b/src/common/api/near/client.ts @@ -10,7 +10,6 @@ import { setupLedger } from "@near-wallet-selector/ledger"; import { setupMathWallet } from "@near-wallet-selector/math-wallet"; import { setupMeteorWallet } from "@near-wallet-selector/meteor-wallet"; import { setupMintbaseWallet } from "@near-wallet-selector/mintbase-wallet"; -import { setupMyNearWallet } from "@near-wallet-selector/my-near-wallet"; import { setupNarwallets } from "@near-wallet-selector/narwallets"; import { setupNearMobileWallet } from "@near-wallet-selector/near-mobile-wallet"; // import { setupNearSnap } from "@near-wallet-selector/near-snap"; @@ -38,7 +37,6 @@ export const naxiosInstance = new naxios({ contractId: SOCIAL_DB_CONTRACT_ACCOUNT_ID, network: NETWORK, walletSelectorModules: [ - setupMyNearWallet(), setupSender(), setupHereWallet(), setupMeteorWallet(), From f2930f5e565968357b6c9ee1ca8b3ab7096b5cd3 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Fri, 24 Jan 2025 13:16:39 +0000 Subject: [PATCH 48/84] Fix: remove reference to a non-existent model --- src/store/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/store/index.ts b/src/store/index.ts index 8686dbf8..b4582970 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -44,7 +44,6 @@ export const useDispatch = () => useReduxDispatch(); export const useGlobalStoreSelector: TypedUseSelectorHook = useSelector; export const resetStore = () => { - dispatch.projectEditor.RESET(); dispatch.nav.RESET(); }; From de7c71ddececc196d98bc23505e9b81eff714f74 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 03:28:21 +0000 Subject: [PATCH 49/84] wip --- src/common/contracts/core/campaigns/client.ts | 2 +- src/common/contracts/core/campaigns/hooks.ts | 13 +++++++++++++ src/common/contracts/core/campaigns/index.ts | 4 +++- src/entities/campaign/components/CampaignBanner.tsx | 2 +- .../campaign/components/CampaignCarouselItem.tsx | 2 +- .../campaign/components/CampaignDonorsTable.tsx | 2 +- .../campaign/components/CampaignSettings.tsx | 3 +-- .../campaign/hooks/{useCampaign.tsx => data.ts} | 0 src/entities/campaign/index.ts | 2 +- 9 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 src/common/contracts/core/campaigns/hooks.ts rename src/entities/campaign/hooks/{useCampaign.tsx => data.ts} (100%) diff --git a/src/common/contracts/core/campaigns/client.ts b/src/common/contracts/core/campaigns/client.ts index 31299d31..4ed80bf0 100644 --- a/src/common/contracts/core/campaigns/client.ts +++ b/src/common/contracts/core/campaigns/client.ts @@ -77,7 +77,7 @@ export const get_campaigns_by_owner = ({ owner_id }: { owner_id: AccountId }) => }); export interface GetCampaignInput { - campaign_id: string; + campaign_id: number; limit?: number; } diff --git a/src/common/contracts/core/campaigns/hooks.ts b/src/common/contracts/core/campaigns/hooks.ts new file mode 100644 index 00000000..2206a0a3 --- /dev/null +++ b/src/common/contracts/core/campaigns/hooks.ts @@ -0,0 +1,13 @@ +import useSWR from "swr"; + +import { IS_CLIENT } from "@/common/constants"; +import type { ByCampaignId, ConditionalActivation } from "@/common/types"; + +import * as contractClient from "./client"; + +export const useCampaign = ({ enabled = true, campaignId }: ByCampaignId & ConditionalActivation) => + useSWR(["useCampaign", campaignId], ([_queryKey, campaignIdKey]) => + !enabled || !IS_CLIENT + ? undefined + : contractClient.get_campaign({ campaign_id: campaignIdKey }), + ); diff --git a/src/common/contracts/core/campaigns/index.ts b/src/common/contracts/core/campaigns/index.ts index 5a984233..948eee7c 100644 --- a/src/common/contracts/core/campaigns/index.ts +++ b/src/common/contracts/core/campaigns/index.ts @@ -1,5 +1,7 @@ import * as campaignsContractClient from "./client"; +import * as campaignsContractHooks from "./hooks"; +export type * from "./hooks"; export * from "./interfaces"; -export { campaignsContractClient }; +export { campaignsContractClient, campaignsContractHooks }; diff --git a/src/entities/campaign/components/CampaignBanner.tsx b/src/entities/campaign/components/CampaignBanner.tsx index 4be82b65..a6d78fd9 100644 --- a/src/entities/campaign/components/CampaignBanner.tsx +++ b/src/entities/campaign/components/CampaignBanner.tsx @@ -7,7 +7,7 @@ import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithF import { Campaign, campaignsContractClient } from "@/common/contracts/core"; import { yoctoNearToFloat } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; -import { SocialsShare } from "@/common/ui/components/molecules/social-share"; +import { SocialsShare } from "@/common/ui/components"; import { AccountProfileLink } from "@/entities/_shared/account"; import { DonateToCampaignProjects } from "@/features/donation"; diff --git a/src/entities/campaign/components/CampaignCarouselItem.tsx b/src/entities/campaign/components/CampaignCarouselItem.tsx index e3867c62..0c7fa223 100644 --- a/src/entities/campaign/components/CampaignCarouselItem.tsx +++ b/src/entities/campaign/components/CampaignCarouselItem.tsx @@ -4,7 +4,7 @@ import { LazyLoadImage } from "react-lazy-load-image-component"; import { Campaign } from "@/common/contracts/core"; import { truncate, yoctoNearToFloat } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; -import { CarouselItem } from "@/common/ui/components/molecules/carousel"; +import { CarouselItem } from "@/common/ui/components"; import { AccountProfileLink } from "@/entities/_shared/account"; import { DonateToCampaignProjects } from "@/features/donation"; diff --git a/src/entities/campaign/components/CampaignDonorsTable.tsx b/src/entities/campaign/components/CampaignDonorsTable.tsx index b37e8659..5e3fb9e0 100644 --- a/src/entities/campaign/components/CampaignDonorsTable.tsx +++ b/src/entities/campaign/components/CampaignDonorsTable.tsx @@ -10,7 +10,7 @@ import { DataTable } from "@/common/ui/components"; import { NearIcon } from "@/common/ui/svg"; import { AccountProfilePicture } from "@/entities/_shared/account"; -import { useCampaign } from "../hooks/useCampaign"; +import { useCampaign } from "../hooks/data"; export const CampaignDonorsTable = () => { const { diff --git a/src/entities/campaign/components/CampaignSettings.tsx b/src/entities/campaign/components/CampaignSettings.tsx index a250b049..f8e6d17c 100644 --- a/src/entities/campaign/components/CampaignSettings.tsx +++ b/src/entities/campaign/components/CampaignSettings.tsx @@ -2,15 +2,14 @@ import { useState } from "react"; import Link from "next/link"; -import { walletApi } from "@/common/api/near/client"; import { useRouteQuery, yoctoNearToFloat } from "@/common/lib"; import { NearIcon } from "@/common/ui/svg"; import { useViewerSession } from "@/common/viewer"; import { AccountProfilePicture } from "@/entities/_shared/account"; import { CampaignForm } from "./CampaignForm"; +import { useCampaign } from "../hooks/data"; import { useCampaignDeploymentRedirect } from "../hooks/redirects"; -import { useCampaign } from "../hooks/useCampaign"; export const CampaignSettings = () => { // TODO: Move this call to the corresponding page! diff --git a/src/entities/campaign/hooks/useCampaign.tsx b/src/entities/campaign/hooks/data.ts similarity index 100% rename from src/entities/campaign/hooks/useCampaign.tsx rename to src/entities/campaign/hooks/data.ts diff --git a/src/entities/campaign/index.ts b/src/entities/campaign/index.ts index d06b64bd..d05e81f7 100644 --- a/src/entities/campaign/index.ts +++ b/src/entities/campaign/index.ts @@ -8,4 +8,4 @@ export * from "./components/CampaignSettings"; export * from "./components/CampaignsList"; export * from "./hooks/redirects"; -export * from "./hooks/useCampaign"; +export * from "./hooks/data"; From cc59ebb56114fe25ae99ccbbd8f0c6e14f58271a Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 05:53:03 +0000 Subject: [PATCH 50/84] wip --- .vscode/settings.json | 3 +- src/common/api/coingecko/hooks.ts | 2 +- src/common/api/near/hooks.ts | 4 +- src/common/contracts/core/campaigns/client.ts | 31 +++--- src/common/contracts/core/campaigns/hooks.ts | 35 +++++- .../contracts/core/campaigns/interfaces.ts | 4 +- src/common/contracts/core/lists/hooks.ts | 4 +- src/common/contracts/core/pot/hooks.ts | 8 +- src/common/contracts/core/voting/hooks.ts | 22 ++-- src/common/contracts/social/hooks.ts | 2 +- src/common/contracts/tokens/ft/hooks.ts | 4 +- src/common/lib/datetime.ts | 6 +- src/common/ui/components/index.ts | 1 + .../ui/components/templates/page-error.tsx | 11 ++ .../campaign/components/CampaignBanner.tsx | 53 +++++---- .../components/CampaignDonorsTable.tsx | 24 ++--- .../campaign/components/CampaignForm.tsx | 4 +- .../campaign/components/CampaignSettings.tsx | 101 +++++++++++------- .../campaign/components/CampaignsList.tsx | 14 +-- src/entities/campaign/hooks/data.ts | 34 ------ src/entities/campaign/hooks/redirects.ts | 2 +- src/entities/campaign/index.ts | 1 - src/entities/project/hooks/lookup.ts | 8 +- src/features/donation/hooks/forms.ts | 4 +- src/layout/campaign/components/layout.tsx | 7 +- .../campaign/[campaignId]/leaderboard.tsx | 15 +-- src/pages/campaign/[campaignId]/settings.tsx | 11 +- src/pages/campaign/create.tsx | 4 +- src/pages/campaigns.tsx | 53 +++++---- src/pages/pot/[potId]/applications.tsx | 10 +- src/pages/profile/[accountId]/campaigns.tsx | 61 ++++++----- 31 files changed, 303 insertions(+), 240 deletions(-) create mode 100644 src/common/ui/components/templates/page-error.tsx delete mode 100644 src/entities/campaign/hooks/data.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 1eb5d927..a01f4e1d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -59,8 +59,8 @@ "datetime", "defi", "dontcare", - "extralight", "Elrond", + "extralight", "fastnear", "foodbank", "formkit", @@ -112,6 +112,7 @@ "wpdas", "xdefi", "yearofchef", + "yocto", "zustand" ] } \ No newline at end of file diff --git a/src/common/api/coingecko/hooks.ts b/src/common/api/coingecko/hooks.ts index 7ab497f0..6636ca70 100644 --- a/src/common/api/coingecko/hooks.ts +++ b/src/common/api/coingecko/hooks.ts @@ -11,7 +11,7 @@ export const useNativeTokenUsdPrice = ( useSWR( () => (disabled ? null : ["oneNativeTokenUsdPrice", NATIVE_TOKEN_ID.toLowerCase()]), - ([_queryKey, tokenId]) => + ([_queryKeyHead, tokenId]) => client .get(`/simple/price?ids=${tokenId}&vs_currencies=usd`) .then((response: { data: { [key: string]: { usd: number } } }) => diff --git a/src/common/api/near/hooks.ts b/src/common/api/near/hooks.ts index b86bf5c9..745cdb53 100644 --- a/src/common/api/near/hooks.ts +++ b/src/common/api/near/hooks.ts @@ -17,7 +17,7 @@ export const useNativeTokenMetadata = ({ disabled = false }: WithDisabled) => useSWR( () => (disabled ? null : ["NativeTokenMetadata", NATIVE_TOKEN_ID]), - (_queryKey) => + (_queryKeyHead) => new Promise((resolve) => resolve({ name: NATIVE_TOKEN_ID, @@ -32,7 +32,7 @@ export const useViewAccount = ({ disabled = false, ...params }: ByAccountId & Wi useSWR( () => (disabled ? null : ["view_account", params.accountId]), - ([_queryKey, accountId]) => + ([_queryKeyHead, accountId]) => nearRpc .query({ request_type: "view_account", diff --git a/src/common/contracts/core/campaigns/client.ts b/src/common/contracts/core/campaigns/client.ts index 4ed80bf0..a2928516 100644 --- a/src/common/contracts/core/campaigns/client.ts +++ b/src/common/contracts/core/campaigns/client.ts @@ -61,28 +61,25 @@ export const donate = (args: DirectCampaignDonationArgs, depositAmountYocto: str callbackUrl: window.location.href, }); -/** - * GET CAMPAIGNS - */ - export const get_campaigns = () => contractApi.view<{}, Campaign[]>("get_campaigns"); -/** - * GET CAMPAIGN - */ +export type GetCampaignsByOwnerArgs = { + owner_id: AccountId; + from_index?: number | null; + limit?: number | null; +}; -export const get_campaigns_by_owner = ({ owner_id }: { owner_id: AccountId }) => - contractApi.view<{}, Campaign[]>("get_campaigns_by_owner", { - args: { owner_id }, - }); +export const get_campaigns_by_owner = (args: GetCampaignsByOwnerArgs) => + contractApi.view("get_campaigns_by_owner", { args }); -export interface GetCampaignInput { +export type GetCampaignArgs = { campaign_id: number; - limit?: number; -} + from_index?: number | null; + limit?: number | null; +}; -export const get_campaign = (args: GetCampaignInput) => - contractApi.view(`get_campaign`, { args }); +export const get_campaign = (args: GetCampaignArgs) => + contractApi.view("get_campaign", { args }); -export const get_donations_for_campaign = (args: GetCampaignInput) => +export const get_donations_for_campaign = (args: GetCampaignArgs) => contractApi.view("get_donations_for_campaign", { args }); diff --git a/src/common/contracts/core/campaigns/hooks.ts b/src/common/contracts/core/campaigns/hooks.ts index 2206a0a3..0c4637ae 100644 --- a/src/common/contracts/core/campaigns/hooks.ts +++ b/src/common/contracts/core/campaigns/hooks.ts @@ -1,13 +1,44 @@ import useSWR from "swr"; import { IS_CLIENT } from "@/common/constants"; -import type { ByCampaignId, ConditionalActivation } from "@/common/types"; +import type { ByAccountId, ByCampaignId, ConditionalActivation } from "@/common/types"; import * as contractClient from "./client"; +export const useCampaigns = ({ enabled = true }: ConditionalActivation | undefined = {}) => + useSWR(["get_campaigns"], () => + !enabled || !IS_CLIENT ? undefined : contractClient.get_campaigns(), + ); + +export const useOwnedCampaigns = ({ + enabled = true, + accountId, + ...params +}: ByAccountId & + Omit & + ConditionalActivation) => + useSWR(["useOwnedCampaigns", accountId, params], ([_queryKeyHead, accountIdKey, paramsKey]) => + !enabled || !IS_CLIENT + ? undefined + : contractClient.get_campaigns_by_owner({ owner_id: accountIdKey, ...paramsKey }), + ); + export const useCampaign = ({ enabled = true, campaignId }: ByCampaignId & ConditionalActivation) => - useSWR(["useCampaign", campaignId], ([_queryKey, campaignIdKey]) => + useSWR(["useCampaign", campaignId], ([_queryKeyHead, campaignIdKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.get_campaign({ campaign_id: campaignIdKey }), ); + +export const useCampaignDonations = ({ + enabled = true, + campaignId, + ...params +}: ByCampaignId & Omit & ConditionalActivation) => + useSWR( + ["useCampaignDonations", campaignId, params], + ([_queryKeyHead, campaignIdKey, paramsKey]) => + !enabled || !IS_CLIENT + ? undefined + : contractClient.get_donations_for_campaign({ campaign_id: campaignIdKey, ...paramsKey }), + ); diff --git a/src/common/contracts/core/campaigns/interfaces.ts b/src/common/contracts/core/campaigns/interfaces.ts index e3010d54..ee0ae302 100644 --- a/src/common/contracts/core/campaigns/interfaces.ts +++ b/src/common/contracts/core/campaigns/interfaces.ts @@ -20,8 +20,8 @@ export type Campaign = { cover_image_url?: string; recipient: AccountId; owner: AccountId; - start_ms?: string; - end_ms?: string; + start_ms: number; + end_ms?: number | null; ftId?: AccountId; target_amount: string; min_amount?: string; diff --git a/src/common/contracts/core/lists/hooks.ts b/src/common/contracts/core/lists/hooks.ts index bcf39ea6..3adf3471 100644 --- a/src/common/contracts/core/lists/hooks.ts +++ b/src/common/contracts/core/lists/hooks.ts @@ -16,7 +16,7 @@ export const useIsRegistered = ({ ConditionalActivation) => useSWR( ["useIsRegistered", accountId, listId, params], - ([_queryKey, accountIdKey, listIdKey, paramsKey]) => + ([_queryKeyHead, accountIdKey, listIdKey, paramsKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.is_registered({ @@ -31,7 +31,7 @@ export const useRegistration = ({ accountId, listId, }: ByAccountId & ByListId & ConditionalActivation) => - useSWR(["useRegistration", accountId, listId], ([_queryKey, accountIdKey, listIdKey]) => + useSWR(["useRegistration", accountId, listId], ([_queryKeyHead, accountIdKey, listIdKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.getRegistration({ registrant_id: accountIdKey, list_id: listIdKey }), diff --git a/src/common/contracts/core/pot/hooks.ts b/src/common/contracts/core/pot/hooks.ts index d05ce8db..1ced4f12 100644 --- a/src/common/contracts/core/pot/hooks.ts +++ b/src/common/contracts/core/pot/hooks.ts @@ -7,21 +7,21 @@ import type { ConditionalActivation } from "@/common/types"; import * as contractClient from "./client"; export const useConfig = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => - useSWR(["useConfig", potId], ([_queryKey, potIdKey]) => + useSWR(["useConfig", potId], ([_queryKeyHead, potIdKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.get_config({ potId: potIdKey }), ); export const useApplications = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => - useSWR(["useApplications", potId], ([_queryKey, potIdKey]) => + useSWR(["useApplications", potId], ([_queryKeyHead, potIdKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.get_applications({ potId: potIdKey }), ); export const usePayouts = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => - useSWR(["usePayouts", potId], ([_queryKey, potIdKey]) => + useSWR(["usePayouts", potId], ([_queryKeyHead, potIdKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.get_payouts({ potId: potIdKey }), ); export const usePayoutChallenges = ({ enabled = true, potId }: ByPotId & ConditionalActivation) => - useSWR(["usePayoutChallenges", potId], ([_queryKey, potIdKey]) => + useSWR(["usePayoutChallenges", potId], ([_queryKeyHead, potIdKey]) => !enabled || !IS_CLIENT ? undefined : contractClient.get_payouts_challenges({ potId: potIdKey }), ); diff --git a/src/common/contracts/core/voting/hooks.ts b/src/common/contracts/core/voting/hooks.ts index cda2e2db..b41840fb 100644 --- a/src/common/contracts/core/voting/hooks.ts +++ b/src/common/contracts/core/voting/hooks.ts @@ -27,7 +27,7 @@ export const useElection = ({ enabled = true, electionId }: BasicElectionQueryKe useSWR( ["get_election", electionId], - ([_queryKey, election_id]: [string, ElectionId]) => + ([_queryKeyHead, election_id]: [string, ElectionId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election({ election_id }), ); @@ -35,7 +35,7 @@ export const useIsVotingPeriod = ({ enabled = true, electionId }: BasicElectionQ useSWR( ["is_voting_period", electionId], - ([_queryKey, election_id]: [string, ElectionId]) => + ([_queryKeyHead, election_id]: [string, ElectionId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.is_voting_period({ election_id }), ); @@ -43,7 +43,7 @@ export const useElectionCandidates = ({ enabled = true, electionId }: BasicElect useSWR( ["get_election_candidates", electionId], - ([_queryKey, election_id]: [string, ElectionId]) => + ([_queryKeyHead, election_id]: [string, ElectionId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election_candidates({ election_id }), @@ -57,22 +57,24 @@ export const useElectionCandidateVotes = ({ useSWR( ["get_candidate_votes", electionId, accountId], - ([_queryKey, election_id, candidate_id]: [string, ElectionId, AccountId]) => + ([_queryKeyHead, election_id, candidate_id]: [string, ElectionId, AccountId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_candidate_votes({ election_id, candidate_id }), ); export const useElectionVotes = ({ enabled = true, electionId }: BasicElectionQueryKey) => - useSWR(["get_election_votes", electionId], ([_queryKey, election_id]: [string, ElectionId]) => - !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election_votes({ election_id }), + useSWR( + ["get_election_votes", electionId], + ([_queryKeyHead, election_id]: [string, ElectionId]) => + !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election_votes({ election_id }), ); export const useElectionVoteCount = ({ enabled = true, electionId }: BasicElectionQueryKey) => useSWR( ["get_election_vote_count", electionId], - ([_queryKey, election_id]: [string, ElectionId]) => + ([_queryKeyHead, election_id]: [string, ElectionId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_election_vote_count({ election_id }), @@ -86,7 +88,7 @@ export const useVotingRoundVoterVotes = ({ useSWR( ["get_voter_votes", electionId, accountId], - ([_queryKey, election_id, voter]: [string, ElectionId, AccountId]) => + ([_queryKeyHead, election_id, voter]: [string, ElectionId, AccountId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_voter_votes({ election_id, voter }), @@ -100,14 +102,14 @@ export const useVoterRemainingCapacity = ({ useSWR( ["get_voter_remaining_capacity", electionId, accountId], - ([_queryKey, election_id, voter]: [string, ElectionId, AccountId]) => + ([_queryKeyHead, election_id, voter]: [string, ElectionId, AccountId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_voter_remaining_capacity({ election_id, voter }), ); export const useUniqueVoters = ({ enabled = true, electionId }: BasicElectionQueryKey) => - useSWR(["get_unique_voters", electionId], ([_queryKey, election_id]: [string, ElectionId]) => + useSWR(["get_unique_voters", electionId], ([_queryKeyHead, election_id]: [string, ElectionId]) => !enabled || !IS_CLIENT ? undefined : votingContractClient.get_unique_voters({ election_id }), ); diff --git a/src/common/contracts/social/hooks.ts b/src/common/contracts/social/hooks.ts index 90225de7..79eba3a3 100644 --- a/src/common/contracts/social/hooks.ts +++ b/src/common/contracts/social/hooks.ts @@ -9,7 +9,7 @@ export const useSocialProfile = ({ enabled = true, accountId, }: ByAccountId & ConditionalActivation) => - useSWR(["useSocialProfile", accountId], ([_queryKey, account_id]) => + useSWR(["useSocialProfile", accountId], ([_queryKeyHead, account_id]) => !enabled || !IS_CLIENT ? undefined : contractClient diff --git a/src/common/contracts/tokens/ft/hooks.ts b/src/common/contracts/tokens/ft/hooks.ts index 572bbc11..ab18a309 100644 --- a/src/common/contracts/tokens/ft/hooks.ts +++ b/src/common/contracts/tokens/ft/hooks.ts @@ -9,7 +9,7 @@ import * as ftClient from "./client"; export const useFtMetadata = ({ disabled = false, ...params }: ByTokenId & WithDisabled) => useSWR( () => (disabled || !IS_CLIENT ? null : ["ft_metadata", params.tokenId]), - ([_queryKey, tokenId]) => ftClient.ft_metadata({ tokenId }).catch(() => undefined), + ([_queryKeyHead, tokenId]) => ftClient.ft_metadata({ tokenId }).catch(() => undefined), ); // TODO: Use conventional `enabled` instead of `disabled` @@ -20,6 +20,6 @@ export const useFtBalanceOf = ({ useSWR( () => (disabled || !IS_CLIENT ? null : ["ft_balance_of", params.accountId, params.tokenId]), - ([_queryKey, accountId, tokenId]) => + ([_queryKeyHead, accountId, tokenId]) => ftClient.ft_balance_of({ accountId, tokenId }).catch(() => undefined), ); diff --git a/src/common/lib/datetime.ts b/src/common/lib/datetime.ts index 2d58dbfd..3781268f 100644 --- a/src/common/lib/datetime.ts +++ b/src/common/lib/datetime.ts @@ -53,10 +53,10 @@ export const daysSinceTimestamp = (unixTimestampMs: number) => Temporal.Now.instant().since(Temporal.Instant.fromEpochMilliseconds(unixTimestampMs)).days; /** - * Sorts a list of objects containing information about events in chronological order - * based on a given datetime property. + * Sorts a list of objects containing information about events + * in chronological order ( old to recent ) based on a given datetime property. */ -export const toChronologicalOrder = ( +export const oldToRecent = ( propertyKey: keyof T, list: Array ? T : T>, ) => diff --git a/src/common/ui/components/index.ts b/src/common/ui/components/index.ts index d7022bdc..7021958f 100644 --- a/src/common/ui/components/index.ts +++ b/src/common/ui/components/index.ts @@ -79,4 +79,5 @@ export * from "./organisms/modal-body"; * * See https://atomicdesign.bradfrost.com/chapter-2/#templates */ +export * from "./templates/page-error"; export * from "./templates/page-with-banner"; diff --git a/src/common/ui/components/templates/page-error.tsx b/src/common/ui/components/templates/page-error.tsx new file mode 100644 index 00000000..1b41fa86 --- /dev/null +++ b/src/common/ui/components/templates/page-error.tsx @@ -0,0 +1,11 @@ +import { RuntimeErrorAlert, type RuntimeErrorAlertProps } from "../molecules/error"; + +export type PageErrorProps = RuntimeErrorAlertProps & {}; + +export const PageError: React.FC = ({ ...props }) => { + return ( +
+ +
+ ); +}; diff --git a/src/entities/campaign/components/CampaignBanner.tsx b/src/entities/campaign/components/CampaignBanner.tsx index a6d78fd9..6aa1f2d3 100644 --- a/src/entities/campaign/components/CampaignBanner.tsx +++ b/src/entities/campaign/components/CampaignBanner.tsx @@ -1,44 +1,35 @@ -import { useEffect, useState } from "react"; - -import { useRouter } from "next/router"; import { LazyLoadImage } from "react-lazy-load-image-component"; import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; -import { Campaign, campaignsContractClient } from "@/common/contracts/core"; +import { campaignsContractHooks } from "@/common/contracts/core"; import { yoctoNearToFloat } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; +import type { ByCampaignId } from "@/common/types"; import { SocialsShare } from "@/common/ui/components"; +import { cn } from "@/common/ui/utils"; import { AccountProfileLink } from "@/entities/_shared/account"; import { DonateToCampaignProjects } from "@/features/donation"; import { CampaignProgressBar } from "./CampaignProgressBar"; -export const CampaignBanner = () => { - const [campaign, setCampaign] = useState(); - const [loading, setLoading] = useState(false); +export type CampaignBannerProps = ByCampaignId & {}; + +export const CampaignBanner: React.FC = ({ campaignId }) => { + const { + isLoading: isCampaignLoading, + data: campaign, + error: campaignLoadingError, + } = campaignsContractHooks.useCampaign({ + campaignId, + }); const usdInfo = useNearToUsdWithFallback( Number(yoctoNearToFloat((campaign?.total_raised_amount as string) || "0")), ); - const { - query: { campaignId }, - } = useRouter(); - - useEffect(() => { - if (!campaignId) return; - setLoading(true); - - campaignsContractClient - .get_campaign({ campaign_id: parseInt(campaignId as string) as any }) - .then((response) => { - setCampaign(response); - }) - .catch((err) => console.error(err)) - .finally(() => setLoading(false)); - }, [campaignId]); - - if (loading) { + // TODO: Use skeletons to cover the loading state instead! + // TODO: Also implement error handling ( when `!isCampaignLoading && campaign === undefined` ) + if (isCampaignLoading) { return
Loading...
; } @@ -58,8 +49,14 @@ export const CampaignBanner = () => { />
{" "}
-

{campaign?.name}

-
+

{campaign?.name}

+ +

FOR

{ disabled={ isStarted || isEnded || campaign?.total_raised_amount === campaign?.max_amount } - campaignId={parseInt(campaignId as string)} + {...{ campaignId }} />
diff --git a/src/entities/campaign/components/CampaignDonorsTable.tsx b/src/entities/campaign/components/CampaignDonorsTable.tsx index 5e3fb9e0..fec130d2 100644 --- a/src/entities/campaign/components/CampaignDonorsTable.tsx +++ b/src/entities/campaign/components/CampaignDonorsTable.tsx @@ -1,28 +1,24 @@ -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import { ColumnDef } from "@tanstack/react-table"; -import { useRouter } from "next/router"; -import { CampaignDonation } from "@/common/contracts/core"; -import { toChronologicalOrder, yoctoNearToFloat } from "@/common/lib"; +import { CampaignDonation, campaignsContractHooks } from "@/common/contracts/core"; +import { oldToRecent, yoctoNearToFloat } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; +import type { ByCampaignId } from "@/common/types"; import { DataTable } from "@/common/ui/components"; import { NearIcon } from "@/common/ui/svg"; import { AccountProfilePicture } from "@/entities/_shared/account"; -import { useCampaign } from "../hooks/data"; +export type CampaignDonorsTableProps = ByCampaignId & {}; -export const CampaignDonorsTable = () => { - const { - query: { campaignId }, - } = useRouter(); - - const { donations } = useCampaign({ campaignId: campaignId as string }); +export const CampaignDonorsTable: React.FC = ({ campaignId }) => { + const { data: donations } = campaignsContractHooks.useCampaignDonations({ campaignId }); const sortedDonations = useMemo(() => { - return toChronologicalOrder("donated_at_ms", donations).toSorted( - (a, b) => b.donated_at_ms - a.donated_at_ms, - ); + if (donations) { + return oldToRecent("donated_at_ms", donations).toReversed(); + } else return []; }, [donations]); const columns: ColumnDef[] = [ diff --git a/src/entities/campaign/components/CampaignForm.tsx b/src/entities/campaign/components/CampaignForm.tsx index 5009e5df..a2308a3e 100644 --- a/src/entities/campaign/components/CampaignForm.tsx +++ b/src/entities/campaign/components/CampaignForm.tsx @@ -9,7 +9,7 @@ import { NearInputField, TextAreaField, TextField } from "@/common/ui/form-field import { useCampaignForm } from "../hooks/forms"; -const formatTimestampForInput = (timestamp: string) => { +const formatTimestampForInput = (timestamp: number) => { if (!timestamp) return ""; const date = new Date(timestamp); return date.toISOString().slice(0, 16); @@ -54,7 +54,7 @@ export const CampaignForm = ({ existingData }: { existingData?: Campaign }) => { existingData?.end_ms ? formatTimestampForInput(existingData?.end_ms) : "", ); } - }, [isUpdate, existingData]); + }, [isUpdate, existingData, form]); const handleCoverImageChange = async (e: ChangeEvent) => { const target = e.target as HTMLInputElement; diff --git a/src/entities/campaign/components/CampaignSettings.tsx b/src/entities/campaign/components/CampaignSettings.tsx index f8e6d17c..c4e466dd 100644 --- a/src/entities/campaign/components/CampaignSettings.tsx +++ b/src/entities/campaign/components/CampaignSettings.tsx @@ -2,36 +2,64 @@ import { useState } from "react"; import Link from "next/link"; -import { useRouteQuery, yoctoNearToFloat } from "@/common/lib"; +import { campaignsContractHooks } from "@/common/contracts/core"; +import { yoctoNearToFloat } from "@/common/lib"; +import type { ByCampaignId } from "@/common/types"; +import { Skeleton } from "@/common/ui/components"; import { NearIcon } from "@/common/ui/svg"; import { useViewerSession } from "@/common/viewer"; import { AccountProfilePicture } from "@/entities/_shared/account"; import { CampaignForm } from "./CampaignForm"; -import { useCampaign } from "../hooks/data"; -import { useCampaignDeploymentRedirect } from "../hooks/redirects"; +import { useCampaignCreateOrUpdateRedirect } from "../hooks/redirects"; -export const CampaignSettings = () => { - // TODO: Move this call to the corresponding page! - useCampaignDeploymentRedirect(); +const formatTime = (timestamp: number) => + new Date(timestamp).toLocaleString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + timeZone: "UTC", + }); - const { - query: { campaignId }, - } = useRouteQuery(); +const CampaignSettingsBarCard = ({ + title, + value, + hasLogo, +}: { + title: string; + value: string; + hasLogo?: boolean; +}) => { + return ( +
+

{title}

+

+ {hasLogo && } + {value} +

+
+ ); +}; + +const CampaignSettingsBarCardSkeleton = () => ; +export type CampaignSettingsProps = ByCampaignId & {}; + +export const CampaignSettings: React.FC = ({ campaignId }) => { const viewer = useViewerSession(); const [openEditCampaign, setOpenEditCampaign] = useState(false); - const { campaign } = useCampaign({ campaignId: campaignId as string }); - if (!campaign) return <>; + const { + isLoading: isCampaignLoading, + data: campaign, + error: campaignLoadingError, + } = campaignsContractHooks.useCampaign({ + campaignId, + }); - const getTime = (timestamp: any) => - new Date(timestamp).toLocaleString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - timeZone: "UTC", - }); + // TODO: Use skeletons to cover the loading state instead! + // TODO: Also implement error handling ( when `!isCampaignLoading && campaign === undefined` ) + if (!campaign) return <>; return (
@@ -39,6 +67,7 @@ export const CampaignSettings = () => {

Organizer

+ {

{campaign?.owner}

+

Recipient

+ {

{campaign?.name}

{campaign?.description}

+
- - - + ) : ( + + )} + + { } hasLogo={!!campaign?.min_amount} /> - {
); }; - -const BarCard = ({ title, value, hasLogo }: { title: string; value: any; hasLogo?: boolean }) => { - return ( -
-

{title}

-

- {hasLogo && } - {value} -

-
- ); -}; diff --git a/src/entities/campaign/components/CampaignsList.tsx b/src/entities/campaign/components/CampaignsList.tsx index 6e71ca5d..46eb12eb 100644 --- a/src/entities/campaign/components/CampaignsList.tsx +++ b/src/entities/campaign/components/CampaignsList.tsx @@ -19,21 +19,15 @@ export const CampaignsList = ({ campaigns }: { campaigns: Campaign[] }) => { switch (sortType) { case "recent": - projects.sort( - (a, b) => - new Date(b.start_ms as string).getTime() - new Date(a.start_ms as string).getTime(), - ); - + projects.sort((a, b) => new Date(b.start_ms).getTime() - new Date(a.start_ms).getTime()); setFilteredCampaigns(projects); break; - case "older": - projects.sort( - (a, b) => - new Date(a.start_ms as string).getTime() - new Date(b.start_ms as string).getTime(), - ); + case "older": + projects.sort((a, b) => new Date(a.start_ms).getTime() - new Date(b.start_ms).getTime()); setFilteredCampaigns(projects); break; + default: break; } diff --git a/src/entities/campaign/hooks/data.ts b/src/entities/campaign/hooks/data.ts deleted file mode 100644 index 779fd68e..00000000 --- a/src/entities/campaign/hooks/data.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Campaign, CampaignDonation, campaignsContractClient } from "@/common/contracts/core"; - -export const useCampaign = ({ campaignId }: { campaignId: string }) => { - const [campaign, setCampaign] = useState(); - const [donations, setDonations] = useState([]); - - useEffect(() => { - campaignsContractClient - .get_campaign({ campaign_id: parseInt(campaignId as string) as any }) - .then((response) => { - setCampaign(response); - }) - .catch((err) => console.error(err)); - }, [campaignId]); - - useEffect(() => { - campaignsContractClient - .get_donations_for_campaign({ - campaign_id: parseInt(campaignId as string) as any, - limit: 999, - }) - .then((response) => { - setDonations(response); - }) - .catch((err) => console.error(err)); - }, [campaignId]); - - return { - donations, - campaign, - }; -}; diff --git a/src/entities/campaign/hooks/redirects.ts b/src/entities/campaign/hooks/redirects.ts index 1c5cae46..c9d3d744 100644 --- a/src/entities/campaign/hooks/redirects.ts +++ b/src/entities/campaign/hooks/redirects.ts @@ -7,7 +7,7 @@ import { dispatch } from "@/store"; import { CampaignFinishModal } from "../components/CampaignFinishModal"; -export const useCampaignDeploymentRedirect = () => { +export const useCampaignCreateOrUpdateRedirect = () => { const resultModal = useModal(CampaignFinishModal); const { diff --git a/src/entities/campaign/index.ts b/src/entities/campaign/index.ts index d05e81f7..11a3e1ab 100644 --- a/src/entities/campaign/index.ts +++ b/src/entities/campaign/index.ts @@ -8,4 +8,3 @@ export * from "./components/CampaignSettings"; export * from "./components/CampaignsList"; export * from "./hooks/redirects"; -export * from "./hooks/data"; diff --git a/src/entities/project/hooks/lookup.ts b/src/entities/project/hooks/lookup.ts index 2a73b735..8c45b57d 100644 --- a/src/entities/project/hooks/lookup.ts +++ b/src/entities/project/hooks/lookup.ts @@ -1,7 +1,7 @@ import { useMemo, useState } from "react"; import { indexer } from "@/common/api/indexer"; -import { toChronologicalOrder } from "@/common/lib"; +import { oldToRecent } from "@/common/lib"; import { ByListId, ChronologicalSortOrder, ChronologicalSortOrderVariant } from "@/common/types"; import { ProjectCategory, ProjectListingStatusVariant } from "../types"; @@ -32,9 +32,11 @@ export const useProjectLookup = ({ listId }: ProjectLookupParams) => { }); const results = useMemo(() => { - const oldToRecent = toChronologicalOrder("submitted_at", listRegistrations?.results ?? []); + const oldToRecentResults = oldToRecent("submitted_at", listRegistrations?.results ?? []); - return sortingOrder === ChronologicalSortOrder.older ? oldToRecent : oldToRecent.toReversed(); + return sortingOrder === ChronologicalSortOrder.older + ? oldToRecentResults + : oldToRecentResults.toReversed(); }, [listRegistrations?.results, sortingOrder]); return { diff --git a/src/features/donation/hooks/forms.ts b/src/features/donation/hooks/forms.ts index 8443cb2f..4b974d3d 100644 --- a/src/features/donation/hooks/forms.ts +++ b/src/features/donation/hooks/forms.ts @@ -9,7 +9,7 @@ import { ZodError } from "zod"; import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PotApplicationStatus, indexer } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; -import { toChronologicalOrder } from "@/common/lib"; +import { oldToRecent } from "@/common/lib"; import { useViewerSession } from "@/common/viewer"; import { dispatch } from "@/store"; @@ -51,7 +51,7 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa ); const defaultPotAccountId = useMemo( - () => toChronologicalOrder("matching_round_end", matchingPots).at(0)?.account, + () => oldToRecent("matching_round_end", matchingPots).at(0)?.account, [matchingPots], ); diff --git a/src/layout/campaign/components/layout.tsx b/src/layout/campaign/components/layout.tsx index 4e1da7f8..10221f5a 100644 --- a/src/layout/campaign/components/layout.tsx +++ b/src/layout/campaign/components/layout.tsx @@ -90,11 +90,11 @@ type ReactLayoutProps = { export const CampaignLayout: React.FC = ({ children }) => { const router = useRouter(); - const pathname = router.pathname; + const { campaignId } = router.query as { campaignId: string }; const tabs = CAMPAIGN_TAB_ROUTES; const [selectedTab, setSelectedTab] = useState( - tabs.find((tab) => pathname.includes(tab.href)) || tabs[0], + tabs.find((tab) => router.pathname.includes(tab.href)) || tabs[0], ); const handleSelectedTab = useCallback( @@ -105,8 +105,9 @@ export const CampaignLayout: React.FC = ({ children }) => { return (
- +
+ { - return ; -}; +export default function CampaignLeaderboardPage() { + const router = useRouter(); + const { campaignId } = router.query as { campaignId: string }; -CampaignLeaderBoard.getLayout = function getLayout(page: ReactElement) { + return ; +} + +CampaignLeaderboardPage.getLayout = function getLayout(page: ReactElement) { return {page}; }; - -export default CampaignLeaderBoard; diff --git a/src/pages/campaign/[campaignId]/settings.tsx b/src/pages/campaign/[campaignId]/settings.tsx index 0f17bea0..e08f3c51 100644 --- a/src/pages/campaign/[campaignId]/settings.tsx +++ b/src/pages/campaign/[campaignId]/settings.tsx @@ -1,10 +1,17 @@ import { ReactElement } from "react"; -import { CampaignSettings } from "@/entities/campaign"; +import { useRouter } from "next/router"; + +import { CampaignSettings, useCampaignCreateOrUpdateRedirect } from "@/entities/campaign"; import { CampaignLayout } from "@/layout/campaign/components/layout"; const Settings = () => { - return ; + const router = useRouter(); + const { campaignId } = router.query as { campaignId: string }; + + useCampaignCreateOrUpdateRedirect(); + + return ; }; Settings.getLayout = function getLayout(page: ReactElement) { diff --git a/src/pages/campaign/create.tsx b/src/pages/campaign/create.tsx index bf1143b4..0f28c0a7 100644 --- a/src/pages/campaign/create.tsx +++ b/src/pages/campaign/create.tsx @@ -1,8 +1,8 @@ import { PageWithBanner } from "@/common/ui/components"; -import { CampaignForm, useCampaignDeploymentRedirect } from "@/entities/campaign"; +import { CampaignForm, useCampaignCreateOrUpdateRedirect } from "@/entities/campaign"; export default function CreateCampaign() { - useCampaignDeploymentRedirect(); + useCampaignCreateOrUpdateRedirect(); return ( diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index 14b3e9dc..362cec27 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -2,17 +2,20 @@ import { useEffect, useState } from "react"; import Link from "next/link"; -import { Campaign, campaignsContractClient } from "@/common/contracts/core"; +import { Campaign, campaignsContractHooks } from "@/common/contracts/core"; import { Button, Carousel, CarouselApi, CarouselContent, + PageError, PageWithBanner, + SplashScreen, } from "@/common/ui/components"; +import { cn } from "@/common/ui/utils"; import { CampaignCarouselItem, CampaignsList } from "@/entities/campaign"; -export const FeaturedCampaigns = ({ data }: { data: Campaign[] }) => { +const FeaturedCampaigns = ({ data }: { data: Campaign[] }) => { const [api, setApi] = useState(); const [current, setCurrent] = useState(0); @@ -63,23 +66,22 @@ export const FeaturedCampaigns = ({ data }: { data: Campaign[] }) => { }; export default function CampaignsPage() { - const [campaigns, setCampaigns] = useState([]); - - useEffect(() => { - campaignsContractClient - .get_campaigns() - .then((fetchedCampaigns) => { - setCampaigns(fetchedCampaigns); - }) - .catch((error) => { - console.log(error); - }); - }, []); + const { + isLoading: isCampaignsListLoading, + data: campaigns, + error: campaignsLoadingError, + } = campaignsContractHooks.useCampaigns(); return ( -
-

Fund Your Ideas

+
+

{"Fund Your Ideas"}

+

{ "Bring your vision to life with a powerful fundraising campaign to support groundbreaking projects. Reach your goals and make a positive impact on your community" @@ -99,8 +101,23 @@ export default function CampaignsPage() {

- - + {campaignsLoadingError !== undefined && ( + + )} + + {campaignsLoadingError === undefined && campaigns === undefined && isCampaignsListLoading && ( + + )} + + {campaignsLoadingError === undefined && campaigns !== undefined && ( + <> + + + + )} ); } diff --git a/src/pages/pot/[potId]/applications.tsx b/src/pages/pot/[potId]/applications.tsx index 30b071d6..0ef43233 100644 --- a/src/pages/pot/[potId]/applications.tsx +++ b/src/pages/pot/[potId]/applications.tsx @@ -6,7 +6,7 @@ import { styled } from "styled-components"; import { PotApplication, indexer } from "@/common/api/indexer"; import { usePot } from "@/common/api/indexer/hooks"; -import { toChronologicalOrder } from "@/common/lib"; +import { oldToRecent } from "@/common/lib"; import type { AccountId } from "@/common/types"; import { FilterChip, SearchBar } from "@/common/ui/components"; import { ProjectListingStatusVariant } from "@/entities/project"; @@ -60,7 +60,7 @@ const ApplicationsTab = () => { const [searchTerm, setSearchTerm] = useState(undefined); const { - isLoading: areApplicationsLoading, + isLoading: isApplicationListLoading, error, data: applications, mutate: refetchApplications, @@ -71,8 +71,8 @@ const ApplicationsTab = () => { }); const sortedResults = useMemo(() => { - const oldToRecent = toChronologicalOrder("submitted_at", applications?.results ?? []); - return oldToRecent.toReversed(); + const oldToRecentResults = oldToRecent("submitted_at", applications?.results ?? []); + return oldToRecentResults.toReversed(); }, [applications?.results]); // Admin - Edit Project @@ -175,7 +175,7 @@ const ApplicationsTab = () => { {potDetail && (
- {!areApplicationsLoading ? ( + {!isApplicationListLoading ? ( sortedResults.map((application: PotApplication) => ( { +export default function ProfileCampaignsTab() { const router = useRouter(); const { accountId } = router.query as { accountId: string }; - const [campaigns, setCampaigns] = useState([]); - - useEffect(() => { - campaignsContractClient - .get_campaigns_by_owner({ owner_id: accountId }) - .then((fetchedCampaigns) => { - setCampaigns(fetchedCampaigns); - }) - .catch((error) => { - console.log(error); - }); - }, [accountId]); + const { + isLoading: isCampaignsListLoading, + data: campaigns, + error: campaignsLoadingError, + } = campaignsContractHooks.useOwnedCampaigns({ accountId }); + + // TODO: Use skeletons instead of the splash screen return (
- {campaigns?.length ? ( -
- {campaigns?.map((data) => )} -
- ) : ( - + {campaignsLoadingError !== undefined && ( + + )} + + {campaignsLoadingError === undefined && campaigns === undefined && isCampaignsListLoading && ( + + )} + + {campaignsLoadingError === undefined && campaigns !== undefined && ( + <> + {campaigns.length > 0 ? ( +
+ {campaigns.map((campaign) => ( + + ))} +
+ ) : ( + + )} + )}
); -}; +} -ProfileCampaigns.getLayout = function getLayout(page: ReactElement) { +ProfileCampaignsTab.getLayout = function getLayout(page: ReactElement) { return {page}; }; - -export default ProfileCampaigns; From 5144e5950c03720cd92e0c313b1f29be67c9f8eb Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 05:59:58 +0000 Subject: [PATCH 51/84] wip --- .../atoms/no-results-placeholder.tsx | 29 +++++++++++++++++ src/common/ui/components/index.ts | 1 + src/pages/profile/[accountId]/campaigns.tsx | 6 ++-- src/pages/profile/[accountId]/lists.tsx | 31 ++----------------- 4 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 src/common/ui/components/atoms/no-results-placeholder.tsx diff --git a/src/common/ui/components/atoms/no-results-placeholder.tsx b/src/common/ui/components/atoms/no-results-placeholder.tsx new file mode 100644 index 00000000..95eea981 --- /dev/null +++ b/src/common/ui/components/atoms/no-results-placeholder.tsx @@ -0,0 +1,29 @@ +import { cn } from "../../utils"; + +export type NoResultsPlaceholderProps = { + text: string; +}; + +export const NoResultsPlaceholder: React.FC = ({ text }) => ( +
+

+ {text} +

+ + pots +
+); diff --git a/src/common/ui/components/index.ts b/src/common/ui/components/index.ts index 7021958f..d314028e 100644 --- a/src/common/ui/components/index.ts +++ b/src/common/ui/components/index.ts @@ -24,6 +24,7 @@ export * from "./atoms/hover-card"; export * from "./atoms/infinite-scroll-trigger"; export * from "./atoms/input"; export * from "./atoms/label"; +export * from "./atoms/no-results-placeholder"; export * from "./atoms/popover"; export * from "./atoms/progress"; export * from "./atoms/scroll-area"; diff --git a/src/pages/profile/[accountId]/campaigns.tsx b/src/pages/profile/[accountId]/campaigns.tsx index 23d5d072..c46ddd97 100644 --- a/src/pages/profile/[accountId]/campaigns.tsx +++ b/src/pages/profile/[accountId]/campaigns.tsx @@ -3,12 +3,10 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; import { campaignsContractHooks } from "@/common/contracts/core"; -import { PageError, SplashScreen } from "@/common/ui/components"; +import { NoResultsPlaceholder, PageError, SplashScreen } from "@/common/ui/components"; import { CampaignCard } from "@/entities/campaign"; import { ProfileLayout } from "@/layout/profile/components/layout"; -import { NoResults } from "./lists"; - export default function ProfileCampaignsTab() { const router = useRouter(); const { accountId } = router.query as { accountId: string }; @@ -42,7 +40,7 @@ export default function ProfileCampaignsTab() { ))}
) : ( - + )} )} diff --git a/src/pages/profile/[accountId]/lists.tsx b/src/pages/profile/[accountId]/lists.tsx index 9078bcc2..ae5b4b1e 100644 --- a/src/pages/profile/[accountId]/lists.tsx +++ b/src/pages/profile/[accountId]/lists.tsx @@ -4,39 +4,14 @@ import { useRouter } from "next/router"; import { indexer } from "@/common/api/indexer"; import type { AccountId } from "@/common/types"; -import { Label, Switch } from "@/common/ui/components"; -import { cn } from "@/common/ui/utils"; +import { Label, NoResultsPlaceholder, Switch } from "@/common/ui/components"; import { ListCard, getRandomBackgroundImage } from "@/entities/list"; import { ProfileLayout } from "@/layout/profile/components/layout"; -export const NoResults = ({ text }: { text: string }) => ( -
-

- {text} -

- - pots -
-); - const ProfileLists = () => { const router = useRouter(); - const [administratedListsOnly, setAdministratedListsOnly] = useState(false); const { accountId } = router.query as { accountId: AccountId }; + const [administratedListsOnly, setAdministratedListsOnly] = useState(false); const { data } = indexer.useLists({ account: accountId, @@ -75,7 +50,7 @@ const ProfileLists = () => { })}
) : ( - + )}
From 0ea2d0904038ff836f774ca4fde6adbff41be339 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 07:55:20 +0000 Subject: [PATCH 52/84] wip --- src/common/_deprecated/useRegistration.ts | 47 ---------- src/common/api/intear-prices/index.ts | 4 - .../hooks.ts | 4 +- src/common/api/intear-token-indexer/index.ts | 3 + .../internal/client.generated.ts | 0 .../internal/config.ts | 2 +- .../types.ts | 0 .../api/{near => near-protocol}/client.ts | 0 .../api/{near => near-protocol}/hooks.ts | 0 .../api/{near => near-protocol}/index.ts | 0 .../api/{near => near-protocol}/web3modal.ts | 0 .../client.ts | 2 +- src/common/api/near-social-indexer/hooks.ts | 60 ++++++++++++ src/common/api/near-social-indexer/index.ts | 9 ++ .../queries.ts | 0 src/common/api/near-social/hooks.ts | 60 ------------ src/common/api/near-social/index.ts | 2 - src/common/contracts/core/campaigns/client.ts | 2 +- src/common/contracts/core/donation/client.ts | 2 +- src/common/contracts/core/lists/client.ts | 2 +- src/common/contracts/core/lists/index.ts | 1 + .../contracts/core/pot-factory/client.ts | 2 +- src/common/contracts/core/pot/client.ts | 2 +- src/common/contracts/core/sybil/index.ts | 2 +- src/common/contracts/core/voting/client.ts | 2 +- .../metapool/liquid-staking/client.ts | 2 +- .../ref-finance/ref-exchange/client.ts | 2 +- src/common/contracts/social/client.ts | 2 +- src/common/contracts/sputnik-dao/index.ts | 2 +- src/common/contracts/tokens/ft/client.ts | 2 +- src/common/lib/protocol-config.ts | 2 +- src/common/services/images.ts | 2 +- src/common/types.ts | 3 + .../viewer/internal/wallet-provider.tsx | 2 +- .../account/components/AccountFollowStats.tsx | 35 +++++++ src/entities/_shared/account/index.ts | 10 +- src/entities/_shared/account/model/schemas.ts | 2 +- src/entities/_shared/token/hooks.ts | 6 +- src/entities/_shared/token/types.ts | 2 +- src/entities/campaign/models/effects.ts | 2 +- src/entities/campaign/models/schema.ts | 2 +- src/entities/dao/utils/validation.ts | 2 +- src/entities/list/components/AccountCard.tsx | 2 +- .../list/components/ListFormDetails.tsx | 2 +- src/entities/list/components/ListHero.tsx | 2 +- src/entities/list/hooks/useAllLists.ts | 2 +- src/entities/list/hooks/useListForm.ts | 2 +- src/entities/list/models/effects.ts | 2 +- src/entities/post/components/PostCard.tsx | 2 +- src/entities/pot/hooks/forms.ts | 2 +- .../project/components/ProjectBanner.tsx | 79 ---------------- src/entities/project/index.ts | 1 - .../donation/components/DonationModal.tsx | 2 +- src/features/donation/models/effects.ts | 2 +- .../matching-pool-contribution/hooks/forms.ts | 2 +- src/features/pot-application/hooks/forms.ts | 2 +- .../pot-configuration/model/effects.ts | 2 +- src/features/profile-setup/models/effects.ts | 2 +- src/layout/components/app-bar.tsx | 2 +- src/layout/components/user-dropdown.tsx | 2 +- src/layout/profile/components/FollowStats.tsx | 34 ------- ...ProfileLayoutControls.tsx => controls.tsx} | 76 ++++++++------- src/layout/profile/components/header.tsx | 94 +++++++++++++++++++ .../{ProfileLayoutHero.tsx => hero.tsx} | 66 ++++++------- src/layout/profile/components/layout.tsx | 26 ++--- src/pages/feed/[account]/[block]/index.tsx | 2 +- src/pages/feed/index.tsx | 2 +- src/pages/pot/[potId]/feed.tsx | 4 +- src/pages/pot/[potId]/votes.tsx | 2 +- src/pages/profile/[accountId]/feed.tsx | 2 +- 70 files changed, 339 insertions(+), 369 deletions(-) delete mode 100644 src/common/_deprecated/useRegistration.ts delete mode 100644 src/common/api/intear-prices/index.ts rename src/common/api/{intear-prices => intear-token-indexer}/hooks.ts (79%) create mode 100644 src/common/api/intear-token-indexer/index.ts rename src/common/api/{intear-prices => intear-token-indexer}/internal/client.generated.ts (100%) rename src/common/api/{intear-prices => intear-token-indexer}/internal/config.ts (63%) rename src/common/api/{intear-prices => intear-token-indexer}/types.ts (100%) rename src/common/api/{near => near-protocol}/client.ts (100%) rename src/common/api/{near => near-protocol}/hooks.ts (100%) rename src/common/api/{near => near-protocol}/index.ts (100%) rename src/common/api/{near => near-protocol}/web3modal.ts (100%) rename src/common/api/{near-social => near-social-indexer}/client.ts (88%) create mode 100644 src/common/api/near-social-indexer/hooks.ts create mode 100644 src/common/api/near-social-indexer/index.ts rename src/common/api/{near-social => near-social-indexer}/queries.ts (100%) delete mode 100644 src/common/api/near-social/hooks.ts delete mode 100644 src/common/api/near-social/index.ts create mode 100644 src/entities/_shared/account/components/AccountFollowStats.tsx delete mode 100644 src/entities/project/components/ProjectBanner.tsx delete mode 100644 src/layout/profile/components/FollowStats.tsx rename src/layout/profile/components/{ProfileLayoutControls.tsx => controls.tsx} (73%) create mode 100644 src/layout/profile/components/header.tsx rename src/layout/profile/components/{ProfileLayoutHero.tsx => hero.tsx} (63%) diff --git a/src/common/_deprecated/useRegistration.ts b/src/common/_deprecated/useRegistration.ts deleted file mode 100644 index f9bcddde..00000000 --- a/src/common/_deprecated/useRegistration.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { useEffect, useState } from "react"; - -import { Registration, RegistrationStatus, listsContractClient } from "@/common/contracts/core"; - -const UNREGISTERED_PROJECT = { - id: "", - registrant_id: "", - list_id: 1, - status: RegistrationStatus.Unregistered, - submitted_ms: 0, - updated_ms: 0, - admin_notes: null, - registrant_notes: null, - registered_by: "", -}; - -/** - * @deprecated Use indexer hooks instead! - */ -export const useRegistration = (projectId: string) => { - const [registration, setRegistration] = useState(UNREGISTERED_PROJECT); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchRegistration = async () => { - try { - if (projectId) { - const registration = - (await listsContractClient.getRegistration({ - registrant_id: projectId, - })) || UNREGISTERED_PROJECT; - - setRegistration(registration); - setLoading(false); - } - } catch (error) { - setError(error); - setLoading(false); - } - }; - - fetchRegistration(); - }, [projectId]); - - return { registration, loading, error, isRegisteredProject: !!registration.id }; -}; diff --git a/src/common/api/intear-prices/index.ts b/src/common/api/intear-prices/index.ts deleted file mode 100644 index 8ea63348..00000000 --- a/src/common/api/intear-prices/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { PRICES_REQUEST_CONFIG } from "./internal/config"; -export * as intearPricesClient from "./internal/client.generated"; -export * as intearPricesHooks from "./hooks"; -export * from "./types"; diff --git a/src/common/api/intear-prices/hooks.ts b/src/common/api/intear-token-indexer/hooks.ts similarity index 79% rename from src/common/api/intear-prices/hooks.ts rename to src/common/api/intear-token-indexer/hooks.ts index 4aa1e805..f935d093 100644 --- a/src/common/api/intear-prices/hooks.ts +++ b/src/common/api/intear-token-indexer/hooks.ts @@ -1,7 +1,7 @@ import { ByTokenId, type WithDisabled } from "@/common/types"; import * as generatedClient from "./internal/client.generated"; -import { PRICES_REQUEST_CONFIG } from "./internal/config"; +import { REQUEST_CONFIG } from "./internal/config"; /** * https://prices.intear.tech/swagger-ui/#/Token%20Prices/get_get_token_price @@ -9,7 +9,7 @@ import { PRICES_REQUEST_CONFIG } from "./internal/config"; export const useTokenUsdPrice = ({ tokenId, disabled = false }: ByTokenId & WithDisabled) => { const queryResult = generatedClient.useGetSuperPrecisePrice( { token_id: tokenId }, - { ...PRICES_REQUEST_CONFIG, swr: { enabled: !disabled } }, + { ...REQUEST_CONFIG, swr: { enabled: !disabled } }, ); return { ...queryResult, data: queryResult.data?.data }; diff --git a/src/common/api/intear-token-indexer/index.ts b/src/common/api/intear-token-indexer/index.ts new file mode 100644 index 00000000..3e093ac7 --- /dev/null +++ b/src/common/api/intear-token-indexer/index.ts @@ -0,0 +1,3 @@ +export * as intearPricesClient from "./internal/client.generated"; +export * as intearTokenIndexerHooks from "./hooks"; +export * from "./types"; diff --git a/src/common/api/intear-prices/internal/client.generated.ts b/src/common/api/intear-token-indexer/internal/client.generated.ts similarity index 100% rename from src/common/api/intear-prices/internal/client.generated.ts rename to src/common/api/intear-token-indexer/internal/client.generated.ts diff --git a/src/common/api/intear-prices/internal/config.ts b/src/common/api/intear-token-indexer/internal/config.ts similarity index 63% rename from src/common/api/intear-prices/internal/config.ts rename to src/common/api/intear-token-indexer/internal/config.ts index 34a2ecb0..2d41b429 100644 --- a/src/common/api/intear-prices/internal/config.ts +++ b/src/common/api/intear-token-indexer/internal/config.ts @@ -3,6 +3,6 @@ import { AxiosRequestConfig } from "axios"; /** * Request config for SWR */ -export const PRICES_REQUEST_CONFIG: Record<"axios", AxiosRequestConfig> = { +export const REQUEST_CONFIG: Record<"axios", AxiosRequestConfig> = { axios: { baseURL: "https://prices.intear.tech" }, }; diff --git a/src/common/api/intear-prices/types.ts b/src/common/api/intear-token-indexer/types.ts similarity index 100% rename from src/common/api/intear-prices/types.ts rename to src/common/api/intear-token-indexer/types.ts diff --git a/src/common/api/near/client.ts b/src/common/api/near-protocol/client.ts similarity index 100% rename from src/common/api/near/client.ts rename to src/common/api/near-protocol/client.ts diff --git a/src/common/api/near/hooks.ts b/src/common/api/near-protocol/hooks.ts similarity index 100% rename from src/common/api/near/hooks.ts rename to src/common/api/near-protocol/hooks.ts diff --git a/src/common/api/near/index.ts b/src/common/api/near-protocol/index.ts similarity index 100% rename from src/common/api/near/index.ts rename to src/common/api/near-protocol/index.ts diff --git a/src/common/api/near/web3modal.ts b/src/common/api/near-protocol/web3modal.ts similarity index 100% rename from src/common/api/near/web3modal.ts rename to src/common/api/near-protocol/web3modal.ts diff --git a/src/common/api/near-social/client.ts b/src/common/api/near-social-indexer/client.ts similarity index 88% rename from src/common/api/near-social/client.ts rename to src/common/api/near-social-indexer/client.ts index f59ef93c..c7df83c7 100644 --- a/src/common/api/near-social/client.ts +++ b/src/common/api/near-social-indexer/client.ts @@ -4,7 +4,7 @@ import axios from "axios"; import { NETWORK, SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; import { ClientConfig } from "@/common/types"; -export const client = axios.create({ +export const nearSocialIndexerClient = axios.create({ baseURL: "https://api.near.social", }); diff --git a/src/common/api/near-social-indexer/hooks.ts b/src/common/api/near-social-indexer/hooks.ts new file mode 100644 index 00000000..fb67df0a --- /dev/null +++ b/src/common/api/near-social-indexer/hooks.ts @@ -0,0 +1,60 @@ +import { AxiosResponse } from "axios"; +import useSWR from "swr"; + +import { AccountId, ByAccountId, type ConditionalActivation } from "@/common/types"; + +import { CLIENT_CONFIG, nearSocialIndexerClient } from "./client"; + +export const useFollowerAccountIds = ({ + enabled = true, + accountId, +}: ByAccountId & ConditionalActivation) => + useSWR( + ["useFollowerAccountIds", "/keys"], + + ([_queryKeyHead, urlKey]) => + !enabled + ? undefined + : nearSocialIndexerClient + .post(urlKey, { + keys: [`*/graph/follow/${accountId}`], + options: { values_only: true }, + }) + .then( + ( + response: AxiosResponse< + Record + >, + ) => Object.keys(response.data), + ), + + CLIENT_CONFIG.swr, + ); + +export const useFollowedAccountIds = ({ + enabled = true, + accountId, +}: ByAccountId & ConditionalActivation) => + useSWR( + ["useFollowedAccountIds", "/keys"], + + ([_queryKeyHead, urlKey]) => + !enabled + ? undefined + : nearSocialIndexerClient + .post(urlKey, { + keys: [`${accountId}/graph/follow/*`], + options: { values_only: true }, + }) + .then( + ( + response: AxiosResponse<{ + [key: AccountId]: { + graph: { follow: { [key: AccountId]: boolean } }; + }; + }>, + ) => Object.keys(response.data[accountId].graph.follow), + ), + + CLIENT_CONFIG.swr, + ); diff --git a/src/common/api/near-social-indexer/index.ts b/src/common/api/near-social-indexer/index.ts new file mode 100644 index 00000000..038ee0cb --- /dev/null +++ b/src/common/api/near-social-indexer/index.ts @@ -0,0 +1,9 @@ +import * as nearSocialIndexerHooks from "./hooks"; + +export type * from "./hooks"; + +export { nearSocialIndexerClient } from "./client"; +export { nearSocialIndexerHooks }; + +// TODO: Move this entire module to `@/common/contracts/social`! +export * from "./queries"; diff --git a/src/common/api/near-social/queries.ts b/src/common/api/near-social-indexer/queries.ts similarity index 100% rename from src/common/api/near-social/queries.ts rename to src/common/api/near-social-indexer/queries.ts diff --git a/src/common/api/near-social/hooks.ts b/src/common/api/near-social/hooks.ts deleted file mode 100644 index 4bb759db..00000000 --- a/src/common/api/near-social/hooks.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { AxiosResponse } from "axios"; -import useSWR, { SWRResponse } from "swr"; - -import { AccountId, ByAccountId } from "@/common/types"; - -import { CLIENT_CONFIG, client } from "./client"; - -export const useFollowerAccountIds = ({ - accountId, -}: Partial): SWRResponse => - useSWR( - ["/keys", "followers"], - - ([url]) => { - if (accountId) { - return client - .post(url, { - keys: [`*/graph/follow/${accountId}`], - options: { values_only: true }, - }) - .then( - ( - response: AxiosResponse< - Record - >, - ) => Object.keys(response.data), - ); - } - }, - - CLIENT_CONFIG.swr, - ); - -export const useFollowedAccountIds = ({ - accountId, -}: Partial): SWRResponse => - useSWR( - ["/keys", "followed"], - - ([url]) => { - if (accountId) { - return client - .post(url, { - keys: [`${accountId}/graph/follow/*`], - options: { values_only: true }, - }) - .then( - ( - response: AxiosResponse<{ - [key: AccountId]: { - graph: { follow: { [key: AccountId]: boolean } }; - }; - }>, - ) => Object.keys(response.data[accountId].graph.follow), - ); - } - }, - - CLIENT_CONFIG.swr, - ); diff --git a/src/common/api/near-social/index.ts b/src/common/api/near-social/index.ts deleted file mode 100644 index 22d46b3e..00000000 --- a/src/common/api/near-social/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * as nearSocial from "./hooks"; -export * from "./queries"; diff --git a/src/common/contracts/core/campaigns/client.ts b/src/common/contracts/core/campaigns/client.ts index a2928516..2f6d9e10 100644 --- a/src/common/contracts/core/campaigns/client.ts +++ b/src/common/contracts/core/campaigns/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { CAMPAIGNS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; diff --git a/src/common/contracts/core/donation/client.ts b/src/common/contracts/core/donation/client.ts index 1a11566c..6e853666 100644 --- a/src/common/contracts/core/donation/client.ts +++ b/src/common/contracts/core/donation/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { DONATION_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FULL_TGAS } from "@/common/constants"; import { diff --git a/src/common/contracts/core/lists/client.ts b/src/common/contracts/core/lists/client.ts index cb24b484..ccd743b0 100644 --- a/src/common/contracts/core/lists/client.ts +++ b/src/common/contracts/core/lists/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { LISTS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; diff --git a/src/common/contracts/core/lists/index.ts b/src/common/contracts/core/lists/index.ts index 8e13c2b6..a69c0d1f 100644 --- a/src/common/contracts/core/lists/index.ts +++ b/src/common/contracts/core/lists/index.ts @@ -1,6 +1,7 @@ import * as listsContractClient from "./client"; import * as listsContractHooks from "./hooks"; +export type * from "./client"; export * from "./interfaces"; export { listsContractClient, listsContractHooks }; diff --git a/src/common/contracts/core/pot-factory/client.ts b/src/common/contracts/core/pot-factory/client.ts index cee00718..da8c8346 100644 --- a/src/common/contracts/core/pot-factory/client.ts +++ b/src/common/contracts/core/pot-factory/client.ts @@ -2,7 +2,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { Big } from "big.js"; import { POT_FACTORY_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FULL_TGAS } from "@/common/constants"; import { ByAccountId } from "@/common/types"; diff --git a/src/common/contracts/core/pot/client.ts b/src/common/contracts/core/pot/client.ts index c0f1bdc4..c4ee8ac2 100644 --- a/src/common/contracts/core/pot/client.ts +++ b/src/common/contracts/core/pot/client.ts @@ -2,7 +2,7 @@ import { MemoryCache, calculateDepositByDataSize } from "@wpdas/naxios"; import { parseNearAmount } from "near-api-js/lib/utils/format"; import { PotId } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FULL_TGAS, ONE_HUNDREDTH_NEAR } from "@/common/constants"; import { diff --git a/src/common/contracts/core/sybil/index.ts b/src/common/contracts/core/sybil/index.ts index cac410a2..8e3f17a0 100644 --- a/src/common/contracts/core/sybil/index.ts +++ b/src/common/contracts/core/sybil/index.ts @@ -2,7 +2,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { Provider } from "near-api-js/lib/providers"; import { SYBIL_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FULL_TGAS, ONE_HUNDREDTH_NEAR, TWO_HUNDREDTHS_NEAR } from "@/common/constants"; import { AccountId, type ByAccountId } from "@/common/types"; diff --git a/src/common/contracts/core/voting/client.ts b/src/common/contracts/core/voting/client.ts index bc78c963..a33dd999 100644 --- a/src/common/contracts/core/voting/client.ts +++ b/src/common/contracts/core/voting/client.ts @@ -1,6 +1,6 @@ import naxios, { MemoryCache } from "@wpdas/naxios"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import type { AccountId, diff --git a/src/common/contracts/metapool/liquid-staking/client.ts b/src/common/contracts/metapool/liquid-staking/client.ts index 2751bee2..39076b4d 100644 --- a/src/common/contracts/metapool/liquid-staking/client.ts +++ b/src/common/contracts/metapool/liquid-staking/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { METAPOOL_LIQUID_STAKING_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { IndivisibleUnits } from "@/common/types"; export const contractApi = naxiosInstance.contractApi({ diff --git a/src/common/contracts/ref-finance/ref-exchange/client.ts b/src/common/contracts/ref-finance/ref-exchange/client.ts index 9e54ffa8..bbb22da7 100644 --- a/src/common/contracts/ref-finance/ref-exchange/client.ts +++ b/src/common/contracts/ref-finance/ref-exchange/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { REF_EXCHANGE_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import type { AccountId } from "@/common/types"; const contractApi = naxiosInstance.contractApi({ diff --git a/src/common/contracts/social/client.ts b/src/common/contracts/social/client.ts index eba4ea4e..5781dd02 100644 --- a/src/common/contracts/social/client.ts +++ b/src/common/contracts/social/client.ts @@ -1,7 +1,7 @@ import { buildTransaction } from "@wpdas/naxios"; import { SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { AccountId } from "@/common/types"; /** diff --git a/src/common/contracts/sputnik-dao/index.ts b/src/common/contracts/sputnik-dao/index.ts index 2e835d27..43f1af8d 100644 --- a/src/common/contracts/sputnik-dao/index.ts +++ b/src/common/contracts/sputnik-dao/index.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; export const getDaoPolicy = async (accountId: string) => { try { diff --git a/src/common/contracts/tokens/ft/client.ts b/src/common/contracts/tokens/ft/client.ts index 060e8080..7e2de8ce 100644 --- a/src/common/contracts/tokens/ft/client.ts +++ b/src/common/contracts/tokens/ft/client.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import type { AccountId, ByAccountId, ByTokenId } from "@/common/types"; import type { FungibleTokenMetadata } from "./interfaces"; diff --git a/src/common/lib/protocol-config.ts b/src/common/lib/protocol-config.ts index a1093add..c33c8841 100644 --- a/src/common/lib/protocol-config.ts +++ b/src/common/lib/protocol-config.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; export type ProtocolConfig = { basis_points: number; diff --git a/src/common/services/images.ts b/src/common/services/images.ts index 555faaff..f378747a 100644 --- a/src/common/services/images.ts +++ b/src/common/services/images.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { Image, socialDbContractClient } from "@/common/contracts/social"; type Props = { diff --git a/src/common/types.ts b/src/common/types.ts index 402780d4..c2437589 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -144,6 +144,9 @@ export type ClearanceCheckResult = | { requirements: BasicRequirement[]; isEveryRequirementSatisfied: boolean; error: null } | { requirements: null; isEveryRequirementSatisfied: false; error: Error }; +/** + * @deprecated Use {@link ConditionalActivation} + */ export interface WithDisabled { disabled?: boolean; } diff --git a/src/common/viewer/internal/wallet-provider.tsx b/src/common/viewer/internal/wallet-provider.tsx index 446086a9..0f9e863d 100644 --- a/src/common/viewer/internal/wallet-provider.tsx +++ b/src/common/viewer/internal/wallet-provider.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect } from "react"; -import { nearClient } from "@/common/api/near"; +import { nearClient } from "@/common/api/near-protocol"; import { useWalletContextStore } from "./wallet-context"; diff --git a/src/entities/_shared/account/components/AccountFollowStats.tsx b/src/entities/_shared/account/components/AccountFollowStats.tsx new file mode 100644 index 00000000..769b4e0b --- /dev/null +++ b/src/entities/_shared/account/components/AccountFollowStats.tsx @@ -0,0 +1,35 @@ +import Link from "next/link"; + +import { nearSocialIndexerHooks } from "@/common/api/near-social-indexer"; +import { ByAccountId } from "@/common/types"; + +export type AccountFollowStatsProps = ByAccountId & {}; + +export const AccountFollowStats: React.FC = ({ accountId }) => { + const { data: followerAccountIds } = nearSocialIndexerHooks.useFollowerAccountIds({ + accountId, + }); + + const { data: followedAccountIds } = nearSocialIndexerHooks.useFollowedAccountIds({ + accountId, + }); + + // TODO: Links should lead to the corresponding Near Social profile pages + return ( +
+ {followerAccountIds && ( + + {followerAccountIds?.length} + {"Followers"} + + )} + + {followedAccountIds && ( + + {followedAccountIds?.length} + {"Following"} + + )} +
+ ); +}; diff --git a/src/entities/_shared/account/index.ts b/src/entities/_shared/account/index.ts index 3df65754..0b852206 100644 --- a/src/entities/_shared/account/index.ts +++ b/src/entities/_shared/account/index.ts @@ -3,11 +3,7 @@ export * from "./constants"; export * from "./components/AccountFollowButton"; export * from "./components/AccountGroup"; - -//! Only exported for backward compatibility -// TODO!: Stop using the model component directly and use the `AccountGroup` integrated flow instead -export * from "./components/AccountGroupEditModal"; - +export * from "./components/AccountFollowStats"; export * from "./components/AccountHandle"; export * from "./components/AccountListItem"; export * from "./components/AccountProfileLink"; @@ -19,3 +15,7 @@ export * from "./components/profile-images"; export * from "./hooks/social-profile"; export * from "./model/schemas"; + +//! Only exported for backward compatibility +// TODO!: Stop using the model component directly and use the `AccountGroup` integrated flow instead +export * from "./components/AccountGroupEditModal"; diff --git a/src/entities/_shared/account/model/schemas.ts b/src/entities/_shared/account/model/schemas.ts index 33b37cba..350e4a8b 100644 --- a/src/entities/_shared/account/model/schemas.ts +++ b/src/entities/_shared/account/model/schemas.ts @@ -2,7 +2,7 @@ import { AccountView } from "near-api-js/lib/providers/provider"; import { string } from "zod"; import { NETWORK } from "@/common/_config"; -import { near, nearRpc } from "@/common/api/near/client"; +import { near, nearRpc } from "@/common/api/near-protocol/client"; const primitive = string().min(5, "Account ID is too short"); diff --git a/src/entities/_shared/token/hooks.ts b/src/entities/_shared/token/hooks.ts index 7c823bed..beb2dd48 100644 --- a/src/entities/_shared/token/hooks.ts +++ b/src/entities/_shared/token/hooks.ts @@ -3,8 +3,8 @@ import { useMemo } from "react"; import { Big } from "big.js"; import { coingeckoHooks } from "@/common/api/coingecko"; -import { intearPricesHooks } from "@/common/api/intear-prices"; -import { nearHooks } from "@/common/api/near"; +import { intearTokenIndexerHooks } from "@/common/api/intear-token-indexer"; +import { nearHooks } from "@/common/api/near-protocol"; import { NATIVE_TOKEN_ID, PLATFORM_LISTED_TOKEN_IDS } from "@/common/constants"; import { refExchangeHooks } from "@/common/contracts/ref-finance"; import { ftHooks } from "@/common/contracts/tokens"; @@ -93,7 +93,7 @@ export const useToken = ({ isLoading: isFtUsdPriceLoading, data: oneFtUsdPrice, error: ftUsdPriceError, - } = intearPricesHooks.useTokenUsdPrice({ + } = intearTokenIndexerHooks.useTokenUsdPrice({ disabled: !enabled || !isValidFtContractAccountId, tokenId, }); diff --git a/src/entities/_shared/token/types.ts b/src/entities/_shared/token/types.ts index 291f95dd..a67ba6fa 100644 --- a/src/entities/_shared/token/types.ts +++ b/src/entities/_shared/token/types.ts @@ -1,4 +1,4 @@ -import type { NativeTokenMetadata } from "@/common/api/near/hooks"; +import type { NativeTokenMetadata } from "@/common/api/near-protocol/hooks"; import type { FungibleTokenMetadata } from "@/common/contracts/tokens"; import type { AccountId, ByTokenId } from "@/common/types"; diff --git a/src/entities/campaign/models/effects.ts b/src/entities/campaign/models/effects.ts index 04ac704a..cd8605c3 100644 --- a/src/entities/campaign/models/effects.ts +++ b/src/entities/campaign/models/effects.ts @@ -1,6 +1,6 @@ import { ExecutionStatus, ExecutionStatusBasic } from "near-api-js/lib/providers/provider"; -import { nearRpc, walletApi } from "@/common/api/near/client"; +import { nearRpc, walletApi } from "@/common/api/near-protocol/client"; import { AppDispatcher } from "@/store"; import { CampaignEnumType } from "../types"; diff --git a/src/entities/campaign/models/schema.ts b/src/entities/campaign/models/schema.ts index 573f6af6..cd56279a 100644 --- a/src/entities/campaign/models/schema.ts +++ b/src/entities/campaign/models/schema.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { NETWORK } from "@/common/_config"; -import { near } from "@/common/api/near/client"; +import { near } from "@/common/api/near-protocol/client"; export const campaignFormSchema = z .object({ diff --git a/src/entities/dao/utils/validation.ts b/src/entities/dao/utils/validation.ts index ebd5810b..54eb06c5 100644 --- a/src/entities/dao/utils/validation.ts +++ b/src/entities/dao/utils/validation.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; type Role = { name: string; diff --git a/src/entities/list/components/AccountCard.tsx b/src/entities/list/components/AccountCard.tsx index ab6f507d..5b44f399 100644 --- a/src/entities/list/components/AccountCard.tsx +++ b/src/entities/list/components/AccountCard.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; import { ListRegistration } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; +import { walletApi } from "@/common/api/near-protocol/client"; import { RegistrationStatus, listsContractClient } from "@/common/contracts/core"; import { truncate } from "@/common/lib"; import { diff --git a/src/entities/list/components/ListFormDetails.tsx b/src/entities/list/components/ListFormDetails.tsx index c196caaa..f4ea84fc 100644 --- a/src/entities/list/components/ListFormDetails.tsx +++ b/src/entities/list/components/ListFormDetails.tsx @@ -7,7 +7,7 @@ import { useRouter } from "next/router"; import { SubmitHandler, useForm } from "react-hook-form"; import { indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; +import { walletApi } from "@/common/api/near-protocol/client"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; import { RegistrationStatus, listsContractClient } from "@/common/contracts/core"; import { nearSocialIpfsUpload } from "@/common/services/ipfs"; diff --git a/src/entities/list/components/ListHero.tsx b/src/entities/list/components/ListHero.tsx index ffcf6fc5..16668e6c 100644 --- a/src/entities/list/components/ListHero.tsx +++ b/src/entities/list/components/ListHero.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/navigation"; -import { walletApi } from "@/common/api/near/client"; +import { walletApi } from "@/common/api/near-protocol/client"; import { Button } from "@/common/ui/components"; import { useAllLists } from "@/entities/list/hooks/useAllLists"; diff --git a/src/entities/list/hooks/useAllLists.ts b/src/entities/list/hooks/useAllLists.ts index f68803e8..7f16e7dc 100644 --- a/src/entities/list/hooks/useAllLists.ts +++ b/src/entities/list/hooks/useAllLists.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { List, indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near/client"; +import { walletApi } from "@/common/api/near-protocol/client"; import { ListOverviewType } from "../types"; diff --git a/src/entities/list/hooks/useListForm.ts b/src/entities/list/hooks/useListForm.ts index 8caccbd2..0fcb26f3 100644 --- a/src/entities/list/hooks/useListForm.ts +++ b/src/entities/list/hooks/useListForm.ts @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; import { prop } from "remeda"; import { LISTS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { listsContractClient } from "@/common/contracts/core"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; diff --git a/src/entities/list/models/effects.ts b/src/entities/list/models/effects.ts index b6adc1e5..64f93bbc 100644 --- a/src/entities/list/models/effects.ts +++ b/src/entities/list/models/effects.ts @@ -1,7 +1,7 @@ import { ExecutionStatusBasic } from "near-api-js/lib/providers/provider"; import { List } from "@/common/api/indexer"; -import { nearRpc, walletApi } from "@/common/api/near/client"; +import { nearRpc, walletApi } from "@/common/api/near-protocol/client"; import { AppDispatcher } from "@/store"; import { ListFormModalType } from "../types"; diff --git a/src/entities/post/components/PostCard.tsx b/src/entities/post/components/PostCard.tsx index 69798c84..3e93154f 100644 --- a/src/entities/post/components/PostCard.tsx +++ b/src/entities/post/components/PostCard.tsx @@ -5,7 +5,7 @@ import { LazyLoadImage } from "react-lazy-load-image-component"; import Markdown from "react-markdown"; import { PotApplicationStatus } from "@/common/api/indexer"; -import { fetchTimeByBlockHeight } from "@/common/api/near-social"; +import { fetchTimeByBlockHeight } from "@/common/api/near-social-indexer"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; import { AccountHandle, AccountProfilePicture } from "@/entities/_shared/account"; import { potApplicationFiltersTags } from "@/features/pot-application"; diff --git a/src/entities/pot/hooks/forms.ts b/src/entities/pot/hooks/forms.ts index e3899a46..7b51c8dc 100644 --- a/src/entities/pot/hooks/forms.ts +++ b/src/entities/pot/hooks/forms.ts @@ -6,7 +6,7 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FULL_TGAS } from "@/common/constants"; import { potContractClient } from "@/common/contracts/core"; diff --git a/src/entities/project/components/ProjectBanner.tsx b/src/entities/project/components/ProjectBanner.tsx deleted file mode 100644 index fae407c7..00000000 --- a/src/entities/project/components/ProjectBanner.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useState } from "react"; - -import { useRegistration } from "@/common/_deprecated/useRegistration"; -import { RegistrationStatus } from "@/common/contracts/core"; -import { ACCOUNT_REGISTRATION_STATUSES } from "@/entities/_shared/account"; - -export const ProjectBanner = ({ projectId }: { projectId: string }) => { - const [toggle, setToggle] = useState(false); - - const { registration, loading } = useRegistration(projectId); - - const registrationStatus = registration - ? ACCOUNT_REGISTRATION_STATUSES[registration.status] - : ACCOUNT_REGISTRATION_STATUSES.Unregistered; - - return loading || registration.status === RegistrationStatus.Approved ? null : ( -
-
-
(registration.admin_notes ? setToggle(!toggle) : "")} - style={{ - color: registrationStatus.textColor, - cursor: registration.admin_notes ? "pointer" : "default", - }} - > - {registrationStatus.text} - {registration.admin_notes && ( -
- (See {toggle ? "Less" : "Why"}) - - - -
- )} -
-
- {registration.admin_notes && ( -
- Admin notes: {registration.admin_notes} -
- )} -
- ); -}; - -export default ProjectBanner; diff --git a/src/entities/project/index.ts b/src/entities/project/index.ts index 775be513..2681dcd8 100644 --- a/src/entities/project/index.ts +++ b/src/entities/project/index.ts @@ -1,6 +1,5 @@ export * from "./constants"; export * from "./types"; -export * from "./components/ProjectBanner"; export * from "./components/ProjectCard"; export * from "./components/ProjectDiscovery"; export * from "./components/Team"; diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index e158ac95..bb238ab6 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { create, useModal } from "@ebay/nice-modal-react"; -import { nearClient } from "@/common/api/near"; +import { nearClient } from "@/common/api/near-protocol"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; diff --git a/src/features/donation/models/effects.ts b/src/features/donation/models/effects.ts index 9a4c9b16..3b4ef12c 100644 --- a/src/features/donation/models/effects.ts +++ b/src/features/donation/models/effects.ts @@ -3,7 +3,7 @@ import axios from "axios"; import { Big } from "big.js"; import { DONATION_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { RPC_NODE_URL, naxiosInstance, walletApi } from "@/common/api/near/client"; +import { RPC_NODE_URL, naxiosInstance, walletApi } from "@/common/api/near-protocol/client"; import { FULL_TGAS, NATIVE_TOKEN_DECIMALS, NATIVE_TOKEN_ID } from "@/common/constants"; import { CampaignDonation, diff --git a/src/features/matching-pool-contribution/hooks/forms.ts b/src/features/matching-pool-contribution/hooks/forms.ts index 44b3c715..78badda5 100644 --- a/src/features/matching-pool-contribution/hooks/forms.ts +++ b/src/features/matching-pool-contribution/hooks/forms.ts @@ -5,7 +5,7 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK, ONE_TGAS } from "@/common/constants"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; diff --git a/src/features/pot-application/hooks/forms.ts b/src/features/pot-application/hooks/forms.ts index 9f75de9f..23daffb4 100644 --- a/src/features/pot-application/hooks/forms.ts +++ b/src/features/pot-application/hooks/forms.ts @@ -6,7 +6,7 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK, ONE_TGAS } from "@/common/constants"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; diff --git a/src/features/pot-configuration/model/effects.ts b/src/features/pot-configuration/model/effects.ts index 6372b9bf..1e45d85b 100644 --- a/src/features/pot-configuration/model/effects.ts +++ b/src/features/pot-configuration/model/effects.ts @@ -2,7 +2,7 @@ import { ExecutionStatusBasic } from "near-api-js/lib/providers/provider"; import { omit } from "remeda"; import { ByPotId } from "@/common/api/indexer"; -import { nearRpc, walletApi } from "@/common/api/near/client"; +import { nearRpc, walletApi } from "@/common/api/near-protocol/client"; import { PotConfig, PotDeploymentResult, diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index 656cf495..a3579867 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -3,7 +3,7 @@ import { Big } from "big.js"; import { parseNearAmount } from "near-api-js/lib/utils/format"; import { LISTS_CONTRACT_ACCOUNT_ID, SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near/client"; +import { naxiosInstance } from "@/common/api/near-protocol/client"; import { FIFTY_TGAS, FULL_TGAS, diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index 699f4cbb..ffc47db5 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; -import { nearClient } from "@/common/api/near"; +import { nearClient } from "@/common/api/near-protocol"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; diff --git a/src/layout/components/user-dropdown.tsx b/src/layout/components/user-dropdown.tsx index a75c6b7d..d1e8d124 100644 --- a/src/layout/components/user-dropdown.tsx +++ b/src/layout/components/user-dropdown.tsx @@ -4,7 +4,7 @@ import { LogOut } from "lucide-react"; import Link from "next/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; -import { nearClient } from "@/common/api/near"; +import { nearClient } from "@/common/api/near-protocol"; import { truncate } from "@/common/lib"; import { Button, diff --git a/src/layout/profile/components/FollowStats.tsx b/src/layout/profile/components/FollowStats.tsx deleted file mode 100644 index fc1d379a..00000000 --- a/src/layout/profile/components/FollowStats.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import Link from "next/link"; - -import { nearSocial } from "@/common/api/near-social"; -import { ByAccountId } from "@/common/types"; - -export type FollowStatsProps = ByAccountId & {}; - -export const FollowStats: React.FC = ({ accountId }) => { - const { data: followerAccountIds } = nearSocial.useFollowerAccountIds({ - accountId, - }); - - const { data: followedAccountIds } = nearSocial.useFollowedAccountIds({ - accountId, - }); - - return ( -
- {followerAccountIds && ( - - {followerAccountIds?.length} - Followers - - )} - - {followedAccountIds && ( - - {followedAccountIds?.length} - Following - - )} -
- ); -}; diff --git a/src/layout/profile/components/ProfileLayoutControls.tsx b/src/layout/profile/components/controls.tsx similarity index 73% rename from src/layout/profile/components/ProfileLayoutControls.tsx rename to src/layout/profile/components/controls.tsx index f09d6696..4af58a73 100644 --- a/src/layout/profile/components/ProfileLayoutControls.tsx +++ b/src/layout/profile/components/controls.tsx @@ -5,10 +5,14 @@ import { CopyToClipboard } from "react-copy-to-clipboard"; import { styled } from "styled-components"; import { useDonationsForProject } from "@/common/_deprecated/useDonationsForProject"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { listsContractHooks } from "@/common/contracts/core"; import { truncate } from "@/common/lib"; +import type { ByAccountId } from "@/common/types"; import { Button, ClipboardCopyButton } from "@/common/ui/components"; import CheckIcon from "@/common/ui/svg/CheckIcon"; import ReferrerIcon from "@/common/ui/svg/ReferrerIcon"; +import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; import { AccountFollowButton, @@ -19,12 +23,7 @@ import { import { useDonation } from "@/features/donation"; import { rootPathnames } from "@/pathnames"; -type Props = { - accountId: string; - isProject: boolean; -}; - -const LinksWrapper = ({ accountId }: { accountId: string }) => { +const Linktree: React.FC = ({ accountId }) => { const viewer = useViewerSession(); const [copied, setCopied] = useState(false); @@ -66,6 +65,7 @@ const LinksWrapper = ({ accountId }: { accountId: string }) => { ); }; +// TODO: Refactor by breaking down into TailwindCSS classes const Container = styled.div` display: flex; flex-direction: column; @@ -87,12 +87,6 @@ const Container = styled.div` line-height: 1; font-family: "Lora"; } - .donors { - font-size: 14px; - span { - font-weight: 600; - } - } } .btn-wrapper { display: flex; @@ -121,36 +115,26 @@ const Container = styled.div` } `; -const DonationsInfo = ({ accountId }: { accountId: string }) => { - const donationsInfo = useDonationsForProject(accountId); - const { openDonationModal } = useDonation({ accountId }); +export type ProfileLayoutControlsProps = ByAccountId & {}; - return ( - -
-
{donationsInfo.usd}
-
- Raised from {donationsInfo.uniqueDonors}{" "} - {donationsInfo.uniqueDonors === 1 ? "donor" : "donors"} -
-
- -
- - -
-
- ); -}; - -export const ProfileLayoutControls = ({ accountId, isProject }: Props) => { +export const ProfileLayoutControls: React.FC = ({ accountId }) => { const viewer = useViewerSession(); const isOwner = viewer?.accountId === accountId; const { profile } = useAccountSocialProfile({ accountId }); + const donationsInfo = useDonationsForProject(accountId); + const { openDonationModal } = useDonation({ accountId }); + + // TODO: For optimization, request and use an indexer endpoint that serves as a proxy for the corresponding function call + const { data: isRegistered } = listsContractHooks.useIsRegistered({ + listId: PUBLIC_GOODS_REGISTRY_LIST_ID, + accountId, + }); return (
{/* NameContainer */}
@@ -171,7 +155,7 @@ export const ProfileLayoutControls = ({ accountId, isProject }: Props) => {
{isOwner && ( -
+
- +
{/* Right */} - {isProject ? ( - + {isRegistered ? ( + +
+
{donationsInfo.usd}
+
+ {"Raised from"} + {donationsInfo.uniqueDonors} + {donationsInfo.uniqueDonors === 1 ? "donor" : "donors"} +
+
+ +
+ + +
+
) : (
diff --git a/src/layout/profile/components/header.tsx b/src/layout/profile/components/header.tsx new file mode 100644 index 00000000..0a65bc49 --- /dev/null +++ b/src/layout/profile/components/header.tsx @@ -0,0 +1,94 @@ +import { useCallback, useState } from "react"; + +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core"; +import type { ByAccountId } from "@/common/types"; +import { cn } from "@/common/ui/utils"; +import { ACCOUNT_REGISTRATION_STATUSES } from "@/entities/_shared/account"; + +export type ProfileLayoutHeaderProps = ByAccountId & {}; + +export const ProfileLayoutHeader: React.FC = ({ accountId }) => { + const [isAdminCommentExpanded, setIsAdminCommentExpanded] = useState(false); + + const toggleAdminComment = useCallback( + () => setIsAdminCommentExpanded(!isAdminCommentExpanded), + [isAdminCommentExpanded], + ); + + // TODO: For optimization, request and use an indexer endpoint for list registration by specified accountId and listId + const { isLoading: isPgRegistryRegistrationLoading, data: pgRegistryRegistration } = + listsContractHooks.useRegistration({ + listId: PUBLIC_GOODS_REGISTRY_LIST_ID, + accountId, + }); + + const registrationStatus = pgRegistryRegistration + ? ACCOUNT_REGISTRATION_STATUSES[pgRegistryRegistration.status] + : ACCOUNT_REGISTRATION_STATUSES.Unregistered; + + return isPgRegistryRegistrationLoading || + pgRegistryRegistration?.status === RegistrationStatus.Approved ? null : ( +
+
+
+ {registrationStatus.text} + + {pgRegistryRegistration?.admin_notes && ( +
+ {`(See ${isAdminCommentExpanded ? "Less" : "Why"})`} + + + + +
+ )} +
+
+ + {pgRegistryRegistration?.admin_notes && ( +
+ {`Admin notes: ${pgRegistryRegistration.admin_notes}`} +
+ )} +
+ ); +}; diff --git a/src/layout/profile/components/ProfileLayoutHero.tsx b/src/layout/profile/components/hero.tsx similarity index 63% rename from src/layout/profile/components/ProfileLayoutHero.tsx rename to src/layout/profile/components/hero.tsx index c5e74431..aaffc2d3 100644 --- a/src/layout/profile/components/ProfileLayoutHero.tsx +++ b/src/layout/profile/components/hero.tsx @@ -1,47 +1,39 @@ +import Image from "next/image"; + import { useIsHuman } from "@/common/_deprecated/useIsHuman"; -import { useRegistration } from "@/common/_deprecated/useRegistration"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { listsContractHooks } from "@/common/contracts/core"; +import type { ByAccountId } from "@/common/types"; import { Avatar, AvatarFallback, AvatarImage, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useAccountSocialProfile } from "@/entities/_shared/account"; +import { AccountFollowStats, useAccountSocialProfile } from "@/entities/_shared/account"; import { listRegistrationStatusIcons } from "@/entities/list"; -import { FollowStats } from "./FollowStats"; - -export type ProfileLayoutHeroProps = { - accountId: string; // near address (donor | project) - isProject: boolean; - imageStyle?: any; - backgroundStyle?: any; - containerStyle?: any; -}; +export type ProfileLayoutHeroProps = ByAccountId & {}; -export const ProfileLayoutHero: React.FC = ({ isProject, accountId }) => { +export const ProfileLayoutHero: React.FC = ({ accountId }) => { + const { isHumanVerified } = useIsHuman(accountId); const { avatarSrc, backgroundSrc } = useAccountSocialProfile({ accountId }); - // get nadabot status on the donor page - let isHumanVerified = false; - const isHuman = useIsHuman(accountId); - - if (!isHuman.loading && !isProject) { - isHumanVerified = isHuman.isHumanVerified; - } - - // get registration if it is on project page - const { registration } = useRegistration(accountId); + // TODO: For optimization, request and use an indexer endpoint for list registration by specified accountId and listId + // TODO: Also implement error and loading status handling + const { + isLoading: isPgRegistryRegistrationLoading, + data: pgRegistryRegistration, + error: pgRegistryRegistrationError, + } = listsContractHooks.useRegistration({ + listId: PUBLIC_GOODS_REGISTRY_LIST_ID, + accountId, + }); return (
- {/* profile Background */}
- {backgroundSrc ? ( - background-image - ) : ( - - )} + Background image
{/* profile image */} @@ -68,20 +60,20 @@ export const ProfileLayoutHero: React.FC = ({ isProject, "relative z-[1] flex -translate-y-5 translate-x-[-25px] items-center gap-2 md:gap-6", )} > - {registration.id ? ( + {pgRegistryRegistration?.id ? (
- {listRegistrationStatusIcons[registration.status].icon} + {listRegistrationStatusIcons[pgRegistryRegistration.status].icon}
- {registration.status} + {pgRegistryRegistration.status}
) : isHumanVerified ? ( @@ -99,7 +91,7 @@ export const ProfileLayoutHero: React.FC = ({ isProject,
)} - +
diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index a54c17b1..e0554663 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -3,14 +3,15 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import { useRegistration } from "@/common/_deprecated/useRegistration"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { listsContractHooks } from "@/common/contracts/core"; import type { AccountId } from "@/common/types"; import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; -import { ProjectBanner } from "@/entities/project"; -import { ProfileLayoutControls } from "./ProfileLayoutControls"; -import { ProfileLayoutHero } from "./ProfileLayoutHero"; +import { ProfileLayoutControls } from "./controls"; +import { ProfileLayoutHeader } from "./header"; +import { ProfileLayoutHero } from "./hero"; const tabRoutesProject = [ { @@ -128,10 +129,13 @@ export const ProfileLayout: React.FC = ({ children }) => { const { query, pathname } = useRouter(); const { accountId } = query as { accountId: AccountId }; - // Load profile data - const { isRegisteredProject } = useRegistration(accountId || ""); + // TODO: For optimization, request and use an indexer endpoint that serves as a proxy for the corresponding function call + const { data: isRegistered } = listsContractHooks.useIsRegistered({ + listId: PUBLIC_GOODS_REGISTRY_LIST_ID, + accountId, + }); - const tabs = isRegisteredProject ? tabRoutesProject : tabRoutesProfile; + const tabs = isRegistered ? tabRoutesProject : tabRoutesProfile; const [selectedTab, setSelectedTab] = useState( tabs.find((tab) => pathname.includes(tab.href)) || tabs[0], @@ -141,13 +145,11 @@ export const ProfileLayout: React.FC = ({ children }) => { setSelectedTab(tabs.find((tab) => pathname.includes(tab.href)) || tabs[0]); }, [pathname, tabs]); - const isProject = isRegisteredProject; - return ( - {isProject && } - - + + + Date: Mon, 27 Jan 2025 09:37:23 +0000 Subject: [PATCH 53/84] wip --- src/common/api/indexer/hooks.ts | 12 ++-- .../profile/_deprecated/DonationItem.tsx | 2 +- .../profile/_deprecated/FundingTable.tsx | 6 +- .../profile/_deprecated/accounts.ts} | 0 .../_deprecated/useDonationsForProject.ts | 5 +- .../profile}/_deprecated/useDonationsSent.ts | 14 ++--- src/layout/profile/components/AboutItem.tsx | 19 ------- src/layout/profile/components/controls.tsx | 43 ++++++++------ .../{Github.tsx => github-repos.tsx} | 8 +-- .../profile/[accountId]/funding-raised.tsx | 50 +++++++++++----- src/pages/profile/[accountId]/home.tsx | 57 ++++++++++++------- 11 files changed, 121 insertions(+), 95 deletions(-) rename src/{common/_deprecated/accounts..ts => layout/profile/_deprecated/accounts.ts} (100%) rename src/{common => layout/profile}/_deprecated/useDonationsForProject.ts (92%) rename src/{common => layout/profile}/_deprecated/useDonationsSent.ts (86%) delete mode 100644 src/layout/profile/components/AboutItem.tsx rename src/layout/profile/components/{Github.tsx => github-repos.tsx} (86%) diff --git a/src/common/api/indexer/hooks.ts b/src/common/api/indexer/hooks.ts index 6783e4b4..9d14637f 100644 --- a/src/common/api/indexer/hooks.ts +++ b/src/common/api/indexer/hooks.ts @@ -166,14 +166,14 @@ export const usePotDonations = ({ * https://test-dev.potlock.io/api/schema/swagger-ui/#/v1/v1_accounts_donations_sent_retrieve */ export const useAccountDonationsSent = ({ + enabled = true, accountId, ...params -}: ByAccountId & generatedClient.V1AccountsDonationsSentRetrieveParams) => { - const queryResult = generatedClient.useV1AccountsDonationsSentRetrieve( - accountId, - params, - INDEXER_CLIENT_CONFIG, - ); +}: ByAccountId & generatedClient.V1AccountsDonationsSentRetrieveParams & ConditionalActivation) => { + const queryResult = generatedClient.useV1AccountsDonationsSentRetrieve(accountId, params, { + ...INDEXER_CLIENT_CONFIG, + swr: { enabled }, + }); return { ...queryResult, data: queryResult.data?.data }; }; diff --git a/src/layout/profile/_deprecated/DonationItem.tsx b/src/layout/profile/_deprecated/DonationItem.tsx index 167101f5..fd40fbc5 100644 --- a/src/layout/profile/_deprecated/DonationItem.tsx +++ b/src/layout/profile/_deprecated/DonationItem.tsx @@ -2,12 +2,12 @@ import Big from "big.js"; import Link from "next/link"; import { styled } from "styled-components"; -import { DonationInfo } from "@/common/_deprecated/accounts."; import { NATIVE_TOKEN_ID } from "@/common/constants"; import { truncate } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; import { useAccountSocialProfile } from "@/entities/_shared/account"; import { TokenIcon } from "@/entities/_shared/token"; +import { DonationInfo } from "@/layout/profile/_deprecated/accounts"; import { rootPathnames } from "@/pathnames"; // TODO: refactor by breaking into TailwindCSS classes diff --git a/src/layout/profile/_deprecated/FundingTable.tsx b/src/layout/profile/_deprecated/FundingTable.tsx index 7e2ef9b3..cbe8017f 100644 --- a/src/layout/profile/_deprecated/FundingTable.tsx +++ b/src/layout/profile/_deprecated/FundingTable.tsx @@ -2,14 +2,14 @@ import { useEffect, useMemo, useState } from "react"; import { styled } from "styled-components"; -import { DonationInfo } from "@/common/_deprecated/accounts."; -import { useDonationsForProject } from "@/common/_deprecated/useDonationsForProject"; -import useDonationsSent from "@/common/_deprecated/useDonationsSent"; import { DeprecatedPagination } from "@/common/ui/components"; import { Arrow } from "@/common/ui/svg"; +import { DonationInfo } from "@/layout/profile/_deprecated/accounts"; +import useDonationsSent from "@/layout/profile/_deprecated/useDonationsSent"; import { DonationItem } from "./DonationItem"; import { FundingStats, Option, Stat } from "./FundingStats"; +import { useDonationsForProject } from "./useDonationsForProject"; // TODO: refactor by breaking into TailwindCSS classes const FundingListContainer = styled.div` diff --git a/src/common/_deprecated/accounts..ts b/src/layout/profile/_deprecated/accounts.ts similarity index 100% rename from src/common/_deprecated/accounts..ts rename to src/layout/profile/_deprecated/accounts.ts diff --git a/src/common/_deprecated/useDonationsForProject.ts b/src/layout/profile/_deprecated/useDonationsForProject.ts similarity index 92% rename from src/common/_deprecated/useDonationsForProject.ts rename to src/layout/profile/_deprecated/useDonationsForProject.ts index f59b87cf..2774f644 100644 --- a/src/common/_deprecated/useDonationsForProject.ts +++ b/src/layout/profile/_deprecated/useDonationsForProject.ts @@ -2,10 +2,9 @@ import { useEffect, useMemo, useState } from "react"; import { Big } from "big.js"; -import { DonationInfo, getAccountDonationsReceived } from "@/common/_deprecated/accounts."; +import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; import { SUPPORTED_FTS } from "@/common/constants"; - -import { useNearToUsdWithFallback } from "./useNearToUsdWithFallback"; +import { DonationInfo, getAccountDonationsReceived } from "@/layout/profile/_deprecated/accounts"; /** * @deprecated Use `indexer.useAccountDonationsReceived` diff --git a/src/common/_deprecated/useDonationsSent.ts b/src/layout/profile/_deprecated/useDonationsSent.ts similarity index 86% rename from src/common/_deprecated/useDonationsSent.ts rename to src/layout/profile/_deprecated/useDonationsSent.ts index c6414250..895f588c 100644 --- a/src/common/_deprecated/useDonationsSent.ts +++ b/src/layout/profile/_deprecated/useDonationsSent.ts @@ -2,23 +2,23 @@ import { useEffect, useMemo, useState } from "react"; import { Big } from "big.js"; -import { Donation } from "@/common/api/indexer"; -import { useAccountDonationsSent } from "@/common/api/indexer/hooks"; +import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; +import { Donation, indexer } from "@/common/api/indexer"; import { SUPPORTED_FTS } from "@/common/constants"; -import { DonationInfo } from "./accounts."; -import { useNearToUsdWithFallback } from "./useNearToUsdWithFallback"; +import { DonationInfo } from "./accounts"; const sortByDate = (donationA: DonationInfo | Donation, donationB: DonationInfo | Donation) => new Date(donationB.donated_at).getTime() - new Date(donationA.donated_at).getTime(); -const useDonationsSent = (accountId?: string) => { +const useDonationsSent = (accountId: string) => { const [donations, setDonations] = useState(); const [directDonations, setDirectDonations] = useState(); const [matchedDonations, setMatchedDonations] = useState(); - const { data: donationsData } = useAccountDonationsSent({ - accountId: accountId || "", + const { data: donationsData } = indexer.useAccountDonationsSent({ + enabled: Boolean(accountId), + accountId: accountId, page_size: 9999, }); diff --git a/src/layout/profile/components/AboutItem.tsx b/src/layout/profile/components/AboutItem.tsx deleted file mode 100644 index 078c37a1..00000000 --- a/src/layout/profile/components/AboutItem.tsx +++ /dev/null @@ -1,19 +0,0 @@ -type Props = { - title: string; - text?: string; - element?: React.ReactNode; -}; - -const AboutItem = ({ title, text, element }: Props) => { - return ( -
-
-

{title}

-
- {text &&

{text}

} - {element && element} -
- ); -}; - -export default AboutItem; diff --git a/src/layout/profile/components/controls.tsx b/src/layout/profile/components/controls.tsx index 4af58a73..407f2604 100644 --- a/src/layout/profile/components/controls.tsx +++ b/src/layout/profile/components/controls.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { CopyToClipboard } from "react-copy-to-clipboard"; import { styled } from "styled-components"; -import { useDonationsForProject } from "@/common/_deprecated/useDonationsForProject"; +import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { listsContractHooks } from "@/common/contracts/core"; import { truncate } from "@/common/lib"; @@ -120,9 +120,8 @@ export type ProfileLayoutControlsProps = ByAccountId & {}; export const ProfileLayoutControls: React.FC = ({ accountId }) => { const viewer = useViewerSession(); const isOwner = viewer?.accountId === accountId; - const { profile } = useAccountSocialProfile({ accountId }); - const donationsInfo = useDonationsForProject(accountId); const { openDonationModal } = useDonation({ accountId }); + const { profile } = useAccountSocialProfile({ accountId }); // TODO: For optimization, request and use an indexer endpoint that serves as a proxy for the corresponding function call const { data: isRegistered } = listsContractHooks.useIsRegistered({ @@ -130,27 +129,36 @@ export const ProfileLayoutControls: React.FC = ({ ac accountId, }); + const { + isLoading: isFundingAccountDataLoading, + data: fundingAccount, + error: fundingAccountDataError, + } = indexer.useAccount({ + enabled: isRegistered, + accountId, + }); + + // TODO: Handle errors and loading state return (
- {/* NameContainer */}
{/* Left */} - {/* NOTE: "grow-1 shrink-1 basis-none" is not working */}
{/* Title */}

{truncate(profile?.name ?? accountId, 28)}

- {/* Account */} +
- {/* Account Id */} -

@ {truncate(accountId, 15)}

- {/* Copy Icon */} + + {`@ ${truncate(accountId, 20)}`} + +
@@ -173,16 +181,19 @@ export const ProfileLayoutControls: React.FC = ({ ac {isRegistered ? (
-
{donationsInfo.usd}
-
- {"Raised from"} - {donationsInfo.uniqueDonors} - {donationsInfo.uniqueDonors === 1 ? "donor" : "donors"} -
+
{`~$${fundingAccount?.total_donations_in_usd}`}
+ + {fundingAccount?.donors_count && ( +
+ {"Raised from"} + {fundingAccount.donors_count} + {fundingAccount.donors_count === 1 ? "donor" : "donors"} +
+ )}
- +
diff --git a/src/layout/profile/components/Github.tsx b/src/layout/profile/components/github-repos.tsx similarity index 86% rename from src/layout/profile/components/Github.tsx rename to src/layout/profile/components/github-repos.tsx index bc2d968c..9ec10cd6 100644 --- a/src/layout/profile/components/Github.tsx +++ b/src/layout/profile/components/github-repos.tsx @@ -10,7 +10,7 @@ const convertURLtoGithubURL = (path: string) => { return path; }; -const getProfileGithubRepositories = (profile?: NEARSocialUserProfile) => { +const formatProfileGithubRepositories = (profile?: NEARSocialUserProfile) => { const githubRepos = (profile?.plGithubRepos ? JSON.parse(profile.plGithubRepos) : []).map( (url: string) => url.replace("github.com/github.com/", "github.com/"), ); @@ -18,8 +18,8 @@ const getProfileGithubRepositories = (profile?: NEARSocialUserProfile) => { return githubRepos as string[]; }; -const Github = ({ profile }: { profile?: NEARSocialUserProfile }) => { - const githubRepositories = getProfileGithubRepositories(profile); +export const ProfileLayoutGithubRepos = ({ profile }: { profile?: NEARSocialUserProfile }) => { + const githubRepositories = formatProfileGithubRepositories(profile); if (githubRepositories.length > 0) { return ( @@ -53,5 +53,3 @@ const Github = ({ profile }: { profile?: NEARSocialUserProfile }) => { return

None provided

; }; - -export default Github; diff --git a/src/pages/profile/[accountId]/funding-raised.tsx b/src/pages/profile/[accountId]/funding-raised.tsx index 347705e0..1e691902 100644 --- a/src/pages/profile/[accountId]/funding-raised.tsx +++ b/src/pages/profile/[accountId]/funding-raised.tsx @@ -1,17 +1,17 @@ import { ReactElement, useState } from "react"; +import Image from "next/image"; import { useRouter } from "next/router"; import { styled } from "styled-components"; -import { useDonationsForProject } from "@/common/_deprecated/useDonationsForProject"; +import { indexer } from "@/common/api/indexer"; import { ExternalFundingSource } from "@/common/contracts/social"; import type { AccountId } from "@/common/types"; -import { useAccountSocialProfile } from "@/entities/_shared/account"; +import { Separator } from "@/common/ui/components"; +import { cn } from "@/common/ui/utils"; import { FundingTable } from "@/layout/profile/_deprecated/FundingTable"; import { ProfileLayout } from "@/layout/profile/components/layout"; -const Line = () =>
; - // TODO: refactor by breaking into TailwindCSS classes export const Container = styled.div` display: flex; @@ -234,31 +234,51 @@ const ExternalFunding = ({ externalFunding }: { externalFunding: ExternalFunding export default function FundingRaisedTab() { const router = useRouter(); const { accountId } = router.query as { accountId: AccountId }; - const { donations } = useDonationsForProject(accountId); - const { profile } = useAccountSocialProfile({ accountId }); - const externalFunding: ExternalFundingSource[] = profile?.plFundingSources - ? JSON.parse(profile?.plFundingSources) + const { + isLoading: isFundingAccountDataLoading, + data: fundingAccount, + error: fundingAccountDataLoadingError, + } = indexer.useAccount({ accountId }); + + const externalFunding: ExternalFundingSource[] = fundingAccount?.near_social_profile_data + ?.plFundingSources + ? JSON.parse(fundingAccount.near_social_profile_data.plFundingSources) : []; - return externalFunding.length === 0 && donations && donations.length === 0 ? ( + // TODO: Handle errors and loading state + + return externalFunding.length === 0 && + fundingAccount && + fundingAccount.total_donations_in_usd === 0 ? ( // No Results -
- + pots +

- No funds have been raised for this project. + {"No funds have been raised for this project."}

) : ( // Container
{externalFunding.length > 0 && } - {externalFunding.length > 0 && donations && donations.length > 0 && } - {donations && donations.length > 0 && } + + {externalFunding.length > 0 && + fundingAccount && + fundingAccount.total_donations_in_usd > 0 && } + + {fundingAccount && fundingAccount.total_donations_in_usd > 0 && ( + + )}
); } diff --git a/src/pages/profile/[accountId]/home.tsx b/src/pages/profile/[accountId]/home.tsx index bc9f34ef..04bd5163 100644 --- a/src/pages/profile/[accountId]/home.tsx +++ b/src/pages/profile/[accountId]/home.tsx @@ -10,10 +10,24 @@ import { isAccountId } from "@/common/lib"; import type { AccountId } from "@/common/types"; import { useAccountSocialProfile } from "@/entities/_shared/account"; import { Team } from "@/entities/project"; -import AboutItem from "@/layout/profile/components/AboutItem"; -import Github from "@/layout/profile/components/Github"; +import { ProfileLayoutGithubRepos } from "@/layout/profile/components/github-repos"; import { ProfileLayout } from "@/layout/profile/components/layout"; -import SmartContract from "@/layout/profile/components/SmartContract"; +import SmartContracts from "@/layout/profile/components/SmartContract"; + +const Section: React.FC<{ title: string; text?: string; children?: React.ReactNode }> = ({ + title, + text, + children, +}) => ( +
+
+

{title}

+
+ + {text &&

{text}

} + {children} +
+); export default function ProfileHomeTab() { const router = useRouter(); @@ -23,8 +37,8 @@ export default function ProfileHomeTab() { const { data: isRegistered = false } = listsContractHooks.useIsRegistered({ enabled: isAccountIdValid, - accountId: accountId ?? "noop", listId: PUBLIC_GOODS_REGISTRY_LIST_ID, + accountId: accountId ?? "noop", }); return ( @@ -36,26 +50,29 @@ export default function ProfileHomeTab() {
- - {profile.description} - - ) : null - } - /> +
+ {profile && ( + + {profile.description} + + )} +
{isRegistered && ( <> - + {profile?.plPublicGoodReason && ( +
+ )} + - } /> - } /> + +
+ +
+ +
+ +
)}
From 2f7ca6aa1e3b3bc62bc7997a9ee494562445d2c7 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 11:28:03 +0000 Subject: [PATCH 54/84] wip --- .vscode/settings.json | 1 + .../components/AccountFollowButton.tsx | 169 ++++++++---------- src/layout/profile/components/hero.tsx | 28 ++- src/layout/profile/components/layout.tsx | 4 +- .../components/{controls.tsx => summary.tsx} | 4 +- src/pages/profile/[accountId]/edit.tsx | 6 - 6 files changed, 95 insertions(+), 117 deletions(-) rename src/layout/profile/components/{controls.tsx => summary.tsx} (97%) diff --git a/.vscode/settings.json b/.vscode/settings.json index a01f4e1d..2c11a3a6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -101,6 +101,7 @@ "svgr", "typecheck", "uidotdev", + "unfollow", "unocss", "upvote", "Upvoted", diff --git a/src/entities/_shared/account/components/AccountFollowButton.tsx b/src/entities/_shared/account/components/AccountFollowButton.tsx index a8ea1fbe..21fc7912 100644 --- a/src/entities/_shared/account/components/AccountFollowButton.tsx +++ b/src/entities/_shared/account/components/AccountFollowButton.tsx @@ -1,8 +1,10 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { nearSocialIndexerHooks } from "@/common/api/near-social-indexer"; import { socialDbContractClient } from "@/common/contracts/social"; import type { ByAccountId } from "@/common/types"; -import { Button } from "@/common/ui/components"; +import { Button, Skeleton } from "@/common/ui/components"; +import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; export type AccountFollowButtonProps = ByAccountId & { @@ -14,102 +16,85 @@ export const AccountFollowButton: React.FC = ({ className, }) => { const viewer = useViewerSession(); - const [followEdge, setFollowEdge] = useState>(); - const [inverseEdge, setInverseEdge] = useState>(); - useEffect(() => { - (async () => { - if (viewer.accountId) { - const _followEdge = await socialDbContractClient.getSocialData>({ - path: `${viewer.accountId}/graph/follow/${accountId}`, - }); + const { + isLoading: isFollowerListLoading, + isValidating: isFollowerListRevalidating, + data: followerAccountIds, + mutate: refetchFollowerAccountIds, + } = nearSocialIndexerHooks.useFollowerAccountIds({ accountId }); + + const { + isLoading: isFollowListLoading, + isValidating: isFollowListRevalidating, + data: followedAccountIds, + mutate: refetchFollowedAccountIds, + } = nearSocialIndexerHooks.useFollowedAccountIds({ accountId }); + + const isSocialIndexLoading = isFollowerListLoading || isFollowListLoading; + const isSocialIndexRevalidating = isFollowerListRevalidating || isFollowListRevalidating; + + const isFollowedByViewer = useMemo( + () => (viewer.isSignedIn ? (followerAccountIds?.includes(viewer.accountId) ?? false) : false), + [followerAccountIds, viewer.accountId, viewer.isSignedIn], + ); - setFollowEdge(_followEdge); + const isFollowingViewer = useMemo( + () => (viewer.isSignedIn ? (followedAccountIds?.includes(viewer.accountId) ?? false) : false), + [followedAccountIds, viewer.accountId, viewer.isSignedIn], + ); - const _inverseEdge = await socialDbContractClient.getSocialData>({ - path: `${accountId}/graph/follow/${viewer.accountId}`, + const actionLabel = useMemo(() => { + if (isFollowedByViewer) { + return "Unfollow"; + } else if (isFollowingViewer) { + return "Follow back"; + } else { + return "Follow"; + } + }, [isFollowedByViewer, isFollowingViewer]); + + const handleFollow = () => { + if (viewer.isSignedIn) { + const requestType = isFollowedByViewer ? "unfollow" : "follow"; + + socialDbContractClient + .setSocialData({ + data: { + [viewer.accountId]: { + graph: { follow: { [accountId]: isFollowedByViewer ? null : "" } }, + + index: { + graph: JSON.stringify({ key: "follow", value: { type: requestType, accountId } }), + notify: JSON.stringify({ key: accountId, value: { type: requestType } }), + }, + }, + }, + }) + .then(() => { + refetchFollowerAccountIds(); + refetchFollowedAccountIds(); + }) + .catch((error) => { + console.log(error); }); - - setInverseEdge(_inverseEdge); - } - })(); - }, [viewer.accountId, accountId]); - - // const loading = followEdge === undefined || inverseEdge === undefined; - const [loading, setLoading] = useState(true); - - useEffect(() => { - setLoading(!followEdge || !inverseEdge); - }, [followEdge, inverseEdge]); - - const follow = followEdge && Object.keys(followEdge).length; - const inverse = inverseEdge && Object.keys(inverseEdge).length; - const type = follow ? "unfollow" : "follow"; - - const data = { - graph: { follow: { [accountId]: follow ? null : "" } }, - index: { - graph: JSON.stringify({ - key: "follow", - value: { - type, - accountId: accountId, - }, - }), - notify: JSON.stringify({ - key: accountId, - value: { - type, - }, - }), - }, - }; - - const [buttonText, setButtonText] = useState("Loading"); - - useEffect(() => { - const _buttonText = loading - ? "Loading" - : follow - ? "Following" - : inverse - ? "Follow back" - : "Follow"; - - setButtonText(_buttonText); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading]); - - const [updating, setUpdating] = useState(false); - - if (!accountId || !viewer?.accountId || viewer.accountId === accountId) { - return ""; - } - - const onClickHandler = async () => { - if (viewer.accountId && buttonText !== "Following") { - setUpdating(true); - - await socialDbContractClient.setSocialData({ - data: { - [viewer.accountId]: data, - }, - }); - - setButtonText("Following"); - setUpdating(false); } }; - return ( - + return !viewer.isSignedIn || viewer.accountId === accountId ? null : ( + <> + {isSocialIndexLoading ? ( + + ) : ( + + )} + ); }; diff --git a/src/layout/profile/components/hero.tsx b/src/layout/profile/components/hero.tsx index aaffc2d3..97c0d508 100644 --- a/src/layout/profile/components/hero.tsx +++ b/src/layout/profile/components/hero.tsx @@ -1,5 +1,3 @@ -import Image from "next/image"; - import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { listsContractHooks } from "@/common/contracts/core"; @@ -29,7 +27,7 @@ export const ProfileLayoutHero: React.FC = ({ accountId return (
- Background image = ({ accountId {pgRegistryRegistration.status}
- ) : isHumanVerified ? ( -
- {listRegistrationStatusIcons.Approved.icon} - -
Verified
-
) : ( -
+ isHumanVerified && ( +
+ {listRegistrationStatusIcons.Approved.icon} + +
Verified
+
+ ) )} diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index e0554663..dfd682dd 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -9,9 +9,9 @@ import type { AccountId } from "@/common/types"; import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; -import { ProfileLayoutControls } from "./controls"; import { ProfileLayoutHeader } from "./header"; import { ProfileLayoutHero } from "./hero"; +import { ProfileLayoutSummary } from "./summary"; const tabRoutesProject = [ { @@ -149,7 +149,7 @@ export const ProfileLayout: React.FC = ({ children }) => { - + = ({ accountId }) => { +export const ProfileLayoutSummary: React.FC = ({ accountId }) => { const viewer = useViewerSession(); const isOwner = viewer?.accountId === accountId; const { openDonationModal } = useDonation({ accountId }); diff --git a/src/pages/profile/[accountId]/edit.tsx b/src/pages/profile/[accountId]/edit.tsx index 59499ce4..7b44ddc5 100644 --- a/src/pages/profile/[accountId]/edit.tsx +++ b/src/pages/profile/[accountId]/edit.tsx @@ -48,12 +48,6 @@ export default function EditProjectPage() { toast({ title: "Success!", description: "You have successfully updated your profile.", - - action: ( - - ), }); }, [router, toast, viewer.accountId]); From ef5e8f4f729ae16898217b6975db891e89a3bc3d Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 12:19:55 +0000 Subject: [PATCH 55/84] wip --- .../account/components/profile-images.tsx | 17 ++-- .../project/components/ProjectCard.tsx | 2 +- .../project/components/ProjectDiscovery.tsx | 8 +- src/layout/profile/components/hero.tsx | 88 +++++++++---------- src/layout/profile/components/summary.tsx | 6 +- src/pages/index.tsx | 21 +++-- 6 files changed, 74 insertions(+), 68 deletions(-) diff --git a/src/entities/_shared/account/components/profile-images.tsx b/src/entities/_shared/account/components/profile-images.tsx index f6f4e782..422ac04b 100644 --- a/src/entities/_shared/account/components/profile-images.tsx +++ b/src/entities/_shared/account/components/profile-images.tsx @@ -29,16 +29,18 @@ export const AccountProfilePicture: React.FC = ({ }; export type AccountProfileCoverProps = ByAccountId & - Pick & { + Required> & { className?: string; }; export const AccountProfileCover: React.FC = ({ accountId, - height = 146, + height, className, }) => { - const { backgroundSrc: src } = useAccountSocialProfile({ accountId }); + const { isLoading: isProfileDataLoading, backgroundSrc: src } = useAccountSocialProfile({ + accountId, + }); const contentClassName = useMemo( () => @@ -50,11 +52,10 @@ export const AccountProfileCover: React.FC = ({ [], ); - return ( -
+ return isProfileDataLoading ? ( + + ) : ( +
- + {/* Content */}
diff --git a/src/entities/project/components/ProjectDiscovery.tsx b/src/entities/project/components/ProjectDiscovery.tsx index ca42a047..63dc3374 100644 --- a/src/entities/project/components/ProjectDiscovery.tsx +++ b/src/entities/project/components/ProjectDiscovery.tsx @@ -4,7 +4,7 @@ import Image from "next/image"; import { ListRegistration } from "@/common/api/indexer"; import { CHRONOLOGICAL_SORT_OPTIONS } from "@/common/constants"; -import { ChronologicalSortOrderVariant } from "@/common/types"; +import { type ByListId, ChronologicalSortOrderVariant } from "@/common/types"; import { Filter, Group, @@ -29,7 +29,9 @@ import { useProjectLookup } from "../hooks/lookup"; const ProjectLookupPlaceholder = () => Array.from({ length: 6 }, (_, index) => ); -export const ProjectDiscovery = () => { +export type ProjectDiscoveryProps = ByListId & {}; + +export const ProjectDiscovery: React.FC = ({ listId }) => { const { projectCategoryFilter, setProjectCategoryFilter, @@ -43,7 +45,7 @@ export const ProjectDiscovery = () => { isProjectLookupPending, projects, totalProjectCount, - } = useProjectLookup({ listId: 1 }); + } = useProjectLookup({ listId }); const tagList = useMemo( () => [ diff --git a/src/layout/profile/components/hero.tsx b/src/layout/profile/components/hero.tsx index 97c0d508..b44abe29 100644 --- a/src/layout/profile/components/hero.tsx +++ b/src/layout/profile/components/hero.tsx @@ -2,16 +2,18 @@ import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { listsContractHooks } from "@/common/contracts/core"; import type { ByAccountId } from "@/common/types"; -import { Avatar, AvatarFallback, AvatarImage, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { AccountFollowStats, useAccountSocialProfile } from "@/entities/_shared/account"; +import { + AccountFollowStats, + AccountProfileCover, + AccountProfilePicture, +} from "@/entities/_shared/account"; import { listRegistrationStatusIcons } from "@/entities/list"; export type ProfileLayoutHeroProps = ByAccountId & {}; export const ProfileLayoutHero: React.FC = ({ accountId }) => { const { isHumanVerified } = useIsHuman(accountId); - const { avatarSrc, backgroundSrc } = useAccountSocialProfile({ accountId }); // TODO: For optimization, request and use an indexer endpoint for list registration by specified accountId and listId // TODO: Also implement error and loading status handling @@ -26,15 +28,8 @@ export const ProfileLayoutHero: React.FC = ({ accountId return (
-
- Background image -
+ - {/* profile image */}
= ({ accountId "max-[400px]:h-[90px] max-[400px]:w-[90px]", )} > - {avatarSrc ? ( - - - - - ) : ( - - )} +
{/* Status */} @@ -58,35 +46,45 @@ export const ProfileLayoutHero: React.FC = ({ accountId "relative z-[1] flex -translate-y-5 translate-x-[-25px] items-center gap-2 md:gap-6", )} > - {pgRegistryRegistration?.id ? ( -
+ {pgRegistryRegistration && ( +
+ {listRegistrationStatusIcons[pgRegistryRegistration.status].icon} + +
+ {pgRegistryRegistration.status} +
+
)} - > - {listRegistrationStatusIcons[pgRegistryRegistration.status].icon} -
- {pgRegistryRegistration.status} -
-
- ) : ( - isHumanVerified && ( -
- {listRegistrationStatusIcons.Approved.icon} + {pgRegistryRegistration === undefined && isHumanVerified && ( +
+ {listRegistrationStatusIcons.Approved.icon} -
Verified
-
- ) +
+ {"Verified"} +
+
+ )} + + ) : ( +
{/** Placeholder */}
)} diff --git a/src/layout/profile/components/summary.tsx b/src/layout/profile/components/summary.tsx index 9a53733f..fd729d7e 100644 --- a/src/layout/profile/components/summary.tsx +++ b/src/layout/profile/components/summary.tsx @@ -121,7 +121,7 @@ export const ProfileLayoutSummary: React.FC = ({ acco const viewer = useViewerSession(); const isOwner = viewer?.accountId === accountId; const { openDonationModal } = useDonation({ accountId }); - const { profile } = useAccountSocialProfile({ accountId }); + const { isLoading: isProfileDataLoading, profile } = useAccountSocialProfile({ accountId }); // TODO: For optimization, request and use an indexer endpoint that serves as a proxy for the corresponding function call const { data: isRegistered } = listsContractHooks.useIsRegistered({ @@ -151,7 +151,9 @@ export const ProfileLayoutSummary: React.FC = ({ acco
{/* Title */}

- {truncate(profile?.name ?? accountId, 28)} + {isProfileDataLoading + ? "Loading account data..." + : truncate(profile?.name ?? accountId, 28)}

diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b24c9044..455ea8a3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { NETWORK } from "@/common/_config"; import { indexer } from "@/common/api/indexer"; +import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; @@ -24,7 +25,7 @@ export const GeneralStats = () => { const { data: stats } = indexer.useStats(); return ( -
+
{`$${stats?.total_donations_usd.toString()}`} @@ -38,7 +39,7 @@ export const GeneralStats = () => {
{/* Line */} -
+
); }; @@ -50,12 +51,12 @@ const WelcomeBanner = () => {
-
+

- Transforming Funding for Public Goods + {"Transforming Funding for Public Goods"}

@@ -88,9 +89,11 @@ const WelcomeBanner = () => { export default function Home() { return ( -
- - +
+
+ + +
@@ -106,7 +109,7 @@ export default function Home() {
- +
); } From 2a989fa770ee56e24af6fa44ea489dd0c6d7758e Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 12:41:50 +0000 Subject: [PATCH 56/84] wip --- .../components/AccountFollowButton.tsx | 2 +- src/entities/project/components/Team.tsx | 5 ++-- src/layout/profile/components/summary.tsx | 27 ++++++++++--------- src/pages/profile/[accountId]/home.tsx | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/entities/_shared/account/components/AccountFollowButton.tsx b/src/entities/_shared/account/components/AccountFollowButton.tsx index 21fc7912..ab01014f 100644 --- a/src/entities/_shared/account/components/AccountFollowButton.tsx +++ b/src/entities/_shared/account/components/AccountFollowButton.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import { nearSocialIndexerHooks } from "@/common/api/near-social-indexer"; import { socialDbContractClient } from "@/common/contracts/social"; diff --git a/src/entities/project/components/Team.tsx b/src/entities/project/components/Team.tsx index aa6b508a..eeb896a5 100644 --- a/src/entities/project/components/Team.tsx +++ b/src/entities/project/components/Team.tsx @@ -78,12 +78,11 @@ export const Team = ({ profile }: Props) => { return (
- {/* col 1 */}

Team members

- {/* col 2 */} -
+ +
{/* Team Members Container */}
{team.length > 0 ? : } diff --git a/src/layout/profile/components/summary.tsx b/src/layout/profile/components/summary.tsx index fd729d7e..c43cc149 100644 --- a/src/layout/profile/components/summary.tsx +++ b/src/layout/profile/components/summary.tsx @@ -149,19 +149,20 @@ export const ProfileLayoutSummary: React.FC = ({ acco {/* Left */}
- {/* Title */} -

- {isProfileDataLoading - ? "Loading account data..." - : truncate(profile?.name ?? accountId, 28)} -

- -
- - {`@ ${truncate(accountId, 20)}`} - - - +
+

+ {isProfileDataLoading + ? "Loading account data..." + : truncate(profile?.name ?? accountId, 36)} +

+ +
+ + {`@ ${truncate(accountId, 36)}`} + + + +
{isOwner && ( diff --git a/src/pages/profile/[accountId]/home.tsx b/src/pages/profile/[accountId]/home.tsx index 04bd5163..f739b63c 100644 --- a/src/pages/profile/[accountId]/home.tsx +++ b/src/pages/profile/[accountId]/home.tsx @@ -20,7 +20,7 @@ const Section: React.FC<{ title: string; text?: string; children?: React.ReactNo children, }) => (
-
+

{title}

From abc31dde48291eb4408fa3bb2196c23d029851cf Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 12:43:47 +0000 Subject: [PATCH 57/84] wip --- src/common/contracts/core/lists/client.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/common/contracts/core/lists/client.ts b/src/common/contracts/core/lists/client.ts index ccd743b0..e05d5b87 100644 --- a/src/common/contracts/core/lists/client.ts +++ b/src/common/contracts/core/lists/client.ts @@ -17,21 +17,11 @@ import { const contractApi = naxiosInstance.contractApi({ contractId: LISTS_CONTRACT_ACCOUNT_ID, - cache: new MemoryCache({ expirationTime: 10 }), // 10 seg + cache: new MemoryCache({ expirationTime: 10 }), }); -// READ METHODS - -/** - * Get lists - */ export const getLists = () => contractApi.view<{}, List[]>("get_lists"); -export const get_admin_list = () => - contractApi.view<{}, List[]>("list_admins_by_list_id", { - args: { list_id: PUBLIC_GOODS_REGISTRY_LIST_ID, accountId: "harrydhillon.near" }, - }); - export const create_list = ({ name, description, From 80d547e281a671f805cfdfa3b4b5032082b524a4 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 12:51:24 +0000 Subject: [PATCH 58/84] wip --- src/common/contracts/core/lists/client.ts | 9 +++------ src/common/contracts/core/lists/interfaces.ts | 10 ++++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/common/contracts/core/lists/client.ts b/src/common/contracts/core/lists/client.ts index e05d5b87..ed714e2a 100644 --- a/src/common/contracts/core/lists/client.ts +++ b/src/common/contracts/core/lists/client.ts @@ -8,7 +8,7 @@ import { AccountId } from "@/common/types"; import { ApplyToList, - GetListInput, + GetListArgs, List, Registration, RegistrationStatus, @@ -20,7 +20,7 @@ const contractApi = naxiosInstance.contractApi({ cache: new MemoryCache({ expirationTime: 10 }), }); -export const getLists = () => contractApi.view<{}, List[]>("get_lists"); +export const get_lists = () => contractApi.view<{}, List[]>("get_lists"); export const create_list = ({ name, @@ -86,10 +86,7 @@ export const update_list = ({ gas: "300000000000000", }); -/** - * Get single list - */ -export const get_list = (args: GetListInput) => +export const get_list = (args: GetListArgs) => contractApi.view("get_list", { args }); export const register_batch = (args: ApplyToList) => diff --git a/src/common/contracts/core/lists/interfaces.ts b/src/common/contracts/core/lists/interfaces.ts index e7990545..029352d0 100644 --- a/src/common/contracts/core/lists/interfaces.ts +++ b/src/common/contracts/core/lists/interfaces.ts @@ -7,8 +7,10 @@ export enum RegistrationStatus { Unregistered = "Unregistered", } +export type ListId = string; + export interface List { - id: number; + id: ListId; name: string; description: string; cover_image_url: null | string; @@ -34,9 +36,9 @@ export interface Registration { registered_by: string; } -export interface GetListInput { - list_id: string; -} +export type GetListArgs = { + list_id: ListId; +}; export interface ApplyToList { list_id: string; From cafbd10bb62eec5d4fa74d1845b16a9e728905c1 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 27 Jan 2025 13:02:10 +0000 Subject: [PATCH 59/84] wip --- src/common/contracts/core/lists/hooks.ts | 5 +++++ src/common/contracts/core/lists/interfaces.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/contracts/core/lists/hooks.ts b/src/common/contracts/core/lists/hooks.ts index 3adf3471..86d0e1bd 100644 --- a/src/common/contracts/core/lists/hooks.ts +++ b/src/common/contracts/core/lists/hooks.ts @@ -5,6 +5,11 @@ import type { ByAccountId, ByListId, ConditionalActivation } from "@/common/type import * as contractClient from "./client"; +export const useList = ({ enabled = true, listId }: ByListId & ConditionalActivation) => + useSWR(["useList", listId], ([_queryKeyHead, listIdKey]) => + !enabled || !IS_CLIENT ? undefined : contractClient.get_list({ list_id: listIdKey }), + ); + export const useIsRegistered = ({ enabled = true, accountId, diff --git a/src/common/contracts/core/lists/interfaces.ts b/src/common/contracts/core/lists/interfaces.ts index 029352d0..07150516 100644 --- a/src/common/contracts/core/lists/interfaces.ts +++ b/src/common/contracts/core/lists/interfaces.ts @@ -7,7 +7,7 @@ export enum RegistrationStatus { Unregistered = "Unregistered", } -export type ListId = string; +export type ListId = number; export interface List { id: ListId; From 81a4995804d018e557770411e54fbc33cb7f2a77 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Wed, 29 Jan 2025 03:47:05 +0000 Subject: [PATCH 60/84] Fix cover image size --- src/entities/list/components/AccountCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entities/list/components/AccountCard.tsx b/src/entities/list/components/AccountCard.tsx index 5b44f399..8953f8cb 100644 --- a/src/entities/list/components/AccountCard.tsx +++ b/src/entities/list/components/AccountCard.tsx @@ -118,7 +118,7 @@ export const AccountCard = ({ Date: Wed, 29 Jan 2025 07:28:03 +0000 Subject: [PATCH 61/84] wip --- .vscode/extensions.json | 3 +- src/common/api/indexer/hooks.ts | 18 +- src/common/contracts/core/lists/client.ts | 17 +- src/common/contracts/core/lists/hooks.ts | 13 ++ .../list/components/ListFormDetails.tsx | 157 +++++++++--------- src/entities/list/hooks/useAllLists.ts | 25 +-- .../components/DonationRecipientShares.tsx | 3 +- src/pages/list/[id]/index.tsx | 9 +- src/pages/list/duplicate/[id].tsx | 7 +- src/pages/list/edit/[id]/index.tsx | 7 +- src/pages/pot/[potId]/feed.tsx | 7 +- 11 files changed, 149 insertions(+), 117 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 84a7e4bb..1c5c61d7 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "naumovs.color-highlight", "usernamehw.errorlens", "eamodio.gitlens", - "dbaeumer.vscode-eslint" + "dbaeumer.vscode-eslint", + "rambit.highlight-counter" ] } \ No newline at end of file diff --git a/src/common/api/indexer/hooks.ts b/src/common/api/indexer/hooks.ts index 9d14637f..e23ac5b3 100644 --- a/src/common/api/indexer/hooks.ts +++ b/src/common/api/indexer/hooks.ts @@ -241,15 +241,14 @@ export const useRandomListRegistration = ({ * https://test-dev.potlock.io/api/schema/swagger-ui/#/v1/v1_lists_registrations_retrieve */ export const useListRegistrations = ({ + enabled = true, listId, ...params -}: Partial & generatedClient.V1ListsRegistrationsRetrieveParams) => { - const queryResult = generatedClient.useV1ListsRegistrationsRetrieve( - listId ?? 0, - params, - - { ...INDEXER_CLIENT_CONFIG, swr: { enabled: Boolean(listId) } }, - ); +}: ByListId & generatedClient.V1ListsRegistrationsRetrieveParams & ConditionalActivation) => { + const queryResult = generatedClient.useV1ListsRegistrationsRetrieve(listId, params, { + ...INDEXER_CLIENT_CONFIG, + swr: { enabled }, + }); return { ...queryResult, data: queryResult.data?.data }; }; @@ -268,12 +267,13 @@ export const useLists = ({ ...params }: generatedClient.V1ListsRetrieveParams = * https://test-dev.potlock.io/api/schema/swagger-ui/#/v1/v1_accounts_upvoted_lists_retrieve */ export const useAccountUpvotedLists = ({ + enabled = true, accountId, ...params -}: { accountId: string } & generatedClient.V1AccountsUpvotedListsRetrieveParams) => { +}: ByAccountId & generatedClient.V1AccountsUpvotedListsRetrieveParams & ConditionalActivation) => { const queryResult = generatedClient.useV1AccountsUpvotedListsRetrieve(accountId, params, { ...INDEXER_CLIENT_CONFIG, - swr: { enabled: Boolean(accountId) }, + swr: { enabled }, }); return { diff --git a/src/common/contracts/core/lists/client.ts b/src/common/contracts/core/lists/client.ts index ed714e2a..fe58ffbc 100644 --- a/src/common/contracts/core/lists/client.ts +++ b/src/common/contracts/core/lists/client.ts @@ -158,16 +158,13 @@ export const get_list_for_owner = (args: { owner_id: string }) => export const get_upvoted_lists_for_account = (args: { account_id: string }) => contractApi.view("get_upvoted_lists_for_account", { args }); -/** - * Get Registrations for a list - */ -export const get_registrations_for_list = ( - args: { list_id: number } = { list_id: PUBLIC_GOODS_REGISTRY_LIST_ID }, -) => contractApi.view("get_registrations_for_list", { args }); - -/** - * Get Registrations for registrant - */ +export type GetRegistrationsForListArgs = { + list_id: number; +}; + +export const get_registrations_for_list = (args: GetRegistrationsForListArgs) => + contractApi.view("get_registrations_for_list", { args }); + export const get_registrations_for_registrant = (args: { list_id?: number; registrant_id: string; diff --git a/src/common/contracts/core/lists/hooks.ts b/src/common/contracts/core/lists/hooks.ts index 86d0e1bd..95311bec 100644 --- a/src/common/contracts/core/lists/hooks.ts +++ b/src/common/contracts/core/lists/hooks.ts @@ -31,6 +31,19 @@ export const useIsRegistered = ({ }), ); +export const useRegistrations = ({ + enabled = true, + listId, + ...params +}: ByListId & + Omit & + ConditionalActivation) => + useSWR(["useRegistrations", listId, params], ([_queryKeyHead, listIdKey, paramsKey]) => + !enabled || !IS_CLIENT + ? undefined + : contractClient.get_registrations_for_list({ list_id: listIdKey, ...paramsKey }), + ); + export const useRegistration = ({ enabled = true, accountId, diff --git a/src/entities/list/components/ListFormDetails.tsx b/src/entities/list/components/ListFormDetails.tsx index f4ea84fc..1cc93bcb 100644 --- a/src/entities/list/components/ListFormDetails.tsx +++ b/src/entities/list/components/ListFormDetails.tsx @@ -5,14 +5,19 @@ import { Check } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/router"; import { SubmitHandler, useForm } from "react-hook-form"; +import { prop } from "remeda"; -import { indexer } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near-protocol/client"; import { IPFS_NEAR_SOCIAL_URL } from "@/common/constants"; -import { RegistrationStatus, listsContractClient } from "@/common/contracts/core"; +import { + RegistrationStatus, + listsContractClient, + listsContractHooks, +} from "@/common/contracts/core"; import { nearSocialIpfsUpload } from "@/common/services/ipfs"; +import type { ByListId } from "@/common/types"; import { Button, Input } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; +import { useViewerSession } from "@/common/viewer"; import { AccountGroup, AccountListItem, AccountProfilePicture } from "@/entities/_shared/account"; import { dispatch } from "@/store"; @@ -40,7 +45,29 @@ interface CreateSuccess { data?: any; } -export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { +export type ListFormProps = Partial & { + isDuplicate?: boolean; +}; + +export const ListFormDetails: React.FC = ({ listId, isDuplicate = false }) => { + const viewer = useViewerSession(); + const { back, push } = useRouter(); + const onEditPage = listId === undefined; + + // TODO: Move to the corresponding page! + useListDeploymentSuccessRedirect(); + + const { isLoading: isRegistrationListLoading, data: registrations } = + listsContractHooks.useRegistrations({ + enabled: listId !== undefined, + listId: listId ?? 0, + }); + + const { isLoading: isListLoading, data: list } = listsContractHooks.useList({ + enabled: listId !== undefined, + listId: listId ?? 0, + }); + const { register, handleSubmit, @@ -51,7 +78,6 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { resolver: zodResolver(createListSchema), }); - useListDeploymentSuccessRedirect(); const descriptionLength = watch("description")?.length || 0; const [coverImage, setCoverImage] = useState(null); @@ -79,70 +105,50 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { accounts, } = useListForm(); - const { - back, - push, - query: { id }, - } = useRouter(); - - const { data, isLoading } = indexer.useListRegistrations({ - ...(isDuplicate && { listId: parseInt(id as string) }), - page_size: 999, - }); - - const onEditPage = !!id; - useEffect(() => { - if (isDuplicate) { - setAccounts(data?.results?.map((registrations) => registrations?.registrant?.id) || []); + if (isDuplicate && registrations !== undefined) { + setAccounts(registrations?.map(prop("registrant_id")) ?? []); } - }, [isDuplicate, isLoading]); + }, [isDuplicate, registrations, setAccounts]); useEffect(() => { - const fetchListDetails = async () => { + if (onEditPage && viewer.isSignedIn && list !== undefined) { try { - const response: any = await listsContractClient.get_list({ - list_id: parseInt(id as string) as any, - }); - - setValue("name", response.name); - setValue("owner", isDuplicate ? walletApi?.accountId : response.owner); - setValue("description", response.description); - setValue("allowApplications", !response.admin_only_registrations); - setValue("approveApplications", response?.default_registration_status === "Approved"); + setValue("name", list.name); + setValue("owner", isDuplicate ? viewer.accountId : list.owner); + setValue("description", list.description); + setValue("allowApplications", !list.admin_only_registrations); + setValue("approveApplications", list.default_registration_status === "Approved"); setAdmins( - isDuplicate && walletApi?.accountId !== response.owner - ? [response?.owner, ...response.admins] - : response.admins, + isDuplicate && viewer.accountId !== list.owner + ? [list?.owner, ...list.admins] + : list.admins, ); - setCoverImage(response.cover_image_url); + setCoverImage(list.cover_image_url); } catch (error) { console.error("Error fetching list details:", error); } - }; - - if (onEditPage) fetchListDetails(); - }, [id, setValue, onEditPage, isDuplicate]); + } + }, [setValue, onEditPage, isDuplicate, setAdmins, viewer.isSignedIn, viewer.accountId, list]); - // prettier-ignore const onSubmit: SubmitHandler = async (data, event) => { // Due to conflicting submit buttons (admin and list), this is to make sure only list submit form is submitted. - dispatch.listEditor.reset() + dispatch.listEditor.reset(); - if ( - (event?.nativeEvent as SubmitEvent)?.submitter?.id !== - "list-submit-button" - ) { return; } + if ((event?.nativeEvent as SubmitEvent)?.submitter?.id !== "list-submit-button") { + return; + } if (onEditPage && !isDuplicate) { - listsContractClient.update_list({ - ...data, - admins, - list_id: parseInt(id as any), - image_cover_url: coverImage || undefined, - }) + listsContractClient + .update_list({ + ...data, + admins, + list_id: listId, + image_cover_url: coverImage || undefined, + }) .then((updatedData) => { setListCreateSuccess({ open: true, @@ -154,17 +160,20 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { console.error("Error updating list:", error); }); - dispatch.listEditor.reset() + dispatch.listEditor.reset(); } else { - listsContractClient.create_list({ - ...data, - admins, - accounts: accounts.map((account) => ({ - registrant_id: account, - status: watch("approveApplications") ? RegistrationStatus.Approved : RegistrationStatus.Pending, - })), - image_cover_url: coverImage, - }) + listsContractClient + .create_list({ + ...data, + admins, + accounts: accounts.map((account) => ({ + registrant_id: account, + status: watch("approveApplications") + ? RegistrationStatus.Approved + : RegistrationStatus.Pending, + })), + image_cover_url: coverImage, + }) .then((dataToReturn) => { setListCreateSuccess({ open: true, @@ -256,10 +265,11 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { [admins], ); - const handleViewList = useCallback(() => push(`/list/${id}`), [id, push]); - + // TODO: Use Next's Link component instead of these callbacks! + const handleViewList = useCallback(() => push(`/list/${listId}`), [listId, push]); const handleViewLists = useCallback(() => push(`/lists`), [push]); + // TODO: Handle loading states for list registrations and list data return ( <>
@@ -333,7 +343,7 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { )}

Permissions

- {onEditPage && watch("owner") === walletApi?.accountId && !isDuplicate && ( + {onEditPage && watch("owner") === viewer.accountId && !isDuplicate && (
{
Owner
- + - {onEditPage ? watch("owner") : walletApi?.accountId} + {onEditPage ? watch("owner") : viewer.accountId}
@@ -396,11 +403,11 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => { isEditable={true} title="Admins" showAccountList={false} - handleRemoveAccounts={id && !isDuplicate ? handleRemoveAdmin : undefined} + handleRemoveAccounts={listId && !isDuplicate ? handleRemoveAdmin : undefined} value={admins.map((admin) => ({ accountId: admin }))} classNames={{ avatar: "w-5 h-5" }} onSubmit={ - id && !isDuplicate + listId && !isDuplicate ? (accounts: string[]) => { const newAdmins = accounts?.filter((admin) => !admins?.includes(admin)) ?? []; @@ -492,7 +499,7 @@ export const ListFormDetails = ({ isDuplicate }: { isDuplicate?: boolean }) => {
- {onEditPage && watch("owner") === walletApi?.accountId && ( + {onEditPage && watch("owner") === viewer.accountId && ( + ) : ( + + + + - return ( - isPotDeploymentAvailable && ( - - ) + + + {"You don't have permission to deploy pots."} + {`Contact ${PLATFORM_NAME} team for details.`} + + + + )} + ); }; diff --git a/src/features/proportional-funding/model/effects.ts b/src/features/proportional-funding/model/effects.ts index 6dc6db5d..631c9f36 100644 --- a/src/features/proportional-funding/model/effects.ts +++ b/src/features/proportional-funding/model/effects.ts @@ -1,7 +1,7 @@ import { values } from "remeda"; import type { ByPotId } from "@/common/api/indexer"; -import { potContractClient } from "@/common/contracts/core"; +import { potContractClient } from "@/common/contracts/core/pot"; import type { FungibleTokenMetadata } from "@/common/contracts/tokens"; import { floatToIndivisible } from "@/common/lib"; import type { VotingRoundParticipants } from "@/entities/voting-round"; diff --git a/src/layout/pot/components/layout-hero.tsx b/src/layout/pot/components/layout-hero.tsx index c5f7d4af..66439449 100644 --- a/src/layout/pot/components/layout-hero.tsx +++ b/src/layout/pot/components/layout-hero.tsx @@ -7,7 +7,7 @@ import remarkGfm from "remark-gfm"; import { ByPotId, indexer } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; -import { potContractHooks } from "@/common/contracts/core"; +import { potContractHooks } from "@/common/contracts/core/pot"; import { Button, Checklist, ClipboardCopyButton, Skeleton } from "@/common/ui/components"; import { VolunteerIcon } from "@/common/ui/svg"; import { cn } from "@/common/ui/utils"; diff --git a/src/layout/profile/components/header.tsx b/src/layout/profile/components/header.tsx index 0a65bc49..22d75625 100644 --- a/src/layout/profile/components/header.tsx +++ b/src/layout/profile/components/header.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from "react"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core"; +import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/lists"; import type { ByAccountId } from "@/common/types"; import { cn } from "@/common/ui/utils"; import { ACCOUNT_REGISTRATION_STATUSES } from "@/entities/_shared/account"; diff --git a/src/layout/profile/components/hero.tsx b/src/layout/profile/components/hero.tsx index b44abe29..7cc3bab0 100644 --- a/src/layout/profile/components/hero.tsx +++ b/src/layout/profile/components/hero.tsx @@ -1,6 +1,6 @@ import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { listsContractHooks } from "@/common/contracts/core"; +import { listsContractHooks } from "@/common/contracts/core/lists"; import type { ByAccountId } from "@/common/types"; import { cn } from "@/common/ui/utils"; import { diff --git a/src/layout/profile/components/layout.tsx b/src/layout/profile/components/layout.tsx index dfd682dd..846b96b7 100644 --- a/src/layout/profile/components/layout.tsx +++ b/src/layout/profile/components/layout.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { listsContractHooks } from "@/common/contracts/core"; +import { listsContractHooks } from "@/common/contracts/core/lists"; import type { AccountId } from "@/common/types"; import { PageWithBanner } from "@/common/ui/components"; import { TabOption } from "@/common/ui/types"; diff --git a/src/layout/profile/components/summary.tsx b/src/layout/profile/components/summary.tsx index c43cc149..aa1dfa7a 100644 --- a/src/layout/profile/components/summary.tsx +++ b/src/layout/profile/components/summary.tsx @@ -6,7 +6,7 @@ import { styled } from "styled-components"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { listsContractHooks } from "@/common/contracts/core"; +import { listsContractHooks } from "@/common/contracts/core/lists"; import { truncate } from "@/common/lib"; import type { ByAccountId } from "@/common/types"; import { Button, ClipboardCopyButton } from "@/common/ui/components"; diff --git a/src/pages/campaigns.tsx b/src/pages/campaigns.tsx index 362cec27..e089894b 100644 --- a/src/pages/campaigns.tsx +++ b/src/pages/campaigns.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; -import { Campaign, campaignsContractHooks } from "@/common/contracts/core"; +import { Campaign, campaignsContractHooks } from "@/common/contracts/core/campaigns"; import { Button, Carousel, diff --git a/src/pages/profile/[accountId]/campaigns.tsx b/src/pages/profile/[accountId]/campaigns.tsx index c46ddd97..fd6095c1 100644 --- a/src/pages/profile/[accountId]/campaigns.tsx +++ b/src/pages/profile/[accountId]/campaigns.tsx @@ -2,7 +2,7 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; -import { campaignsContractHooks } from "@/common/contracts/core"; +import { campaignsContractHooks } from "@/common/contracts/core/campaigns"; import { NoResultsPlaceholder, PageError, SplashScreen } from "@/common/ui/components"; import { CampaignCard } from "@/entities/campaign"; import { ProfileLayout } from "@/layout/profile/components/layout"; diff --git a/src/pages/profile/[accountId]/home.tsx b/src/pages/profile/[accountId]/home.tsx index f739b63c..3c5276b2 100644 --- a/src/pages/profile/[accountId]/home.tsx +++ b/src/pages/profile/[accountId]/home.tsx @@ -5,7 +5,7 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { listsContractHooks } from "@/common/contracts/core"; +import { listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; import type { AccountId } from "@/common/types"; import { useAccountSocialProfile } from "@/entities/_shared/account"; From fd201be128e9f7cb231d6df4c4f39d8eadae687e Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Wed, 29 Jan 2025 13:29:36 +0000 Subject: [PATCH 63/84] wip --- src/common/api/near-protocol/index.ts | 4 +- src/common/contracts/core/pot/client.ts | 4 +- src/common/contracts/core/sybil/index.ts | 6 +-- .../viewer/internal/wallet-provider.tsx | 28 ++++++------- .../pot/components/PotPayoutChallenges.tsx | 42 ++++++++----------- .../donation/components/DonationModal.tsx | 4 +- src/layout/components/app-bar.tsx | 4 +- src/layout/components/user-dropdown.tsx | 4 +- src/pages/pot/[potId]/votes.tsx | 4 +- 9 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/common/api/near-protocol/index.ts b/src/common/api/near-protocol/index.ts index fec41719..ad2db8c1 100644 --- a/src/common/api/near-protocol/index.ts +++ b/src/common/api/near-protocol/index.ts @@ -1,4 +1,4 @@ -import * as nearClient from "./client"; +import * as nearProtocolClient from "./client"; import * as nearHooks from "./hooks"; -export { nearClient, nearHooks }; +export { nearProtocolClient, nearHooks }; diff --git a/src/common/contracts/core/pot/client.ts b/src/common/contracts/core/pot/client.ts index 1a23fb54..b0d9e420 100644 --- a/src/common/contracts/core/pot/client.ts +++ b/src/common/contracts/core/pot/client.ts @@ -2,7 +2,7 @@ import { MemoryCache, calculateDepositByDataSize } from "@wpdas/naxios"; import { parseNearAmount } from "near-api-js/lib/utils/format"; import { PotId } from "@/common/api/indexer"; -import { nearClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { FULL_TGAS, ONE_HUNDREDTH_NEAR } from "@/common/constants"; import { @@ -19,7 +19,7 @@ import { } from "./interfaces"; const contractApi = (potId: string) => - nearClient.naxiosInstance.contractApi({ + nearProtocolClient.naxiosInstance.contractApi({ contractId: potId, cache: new MemoryCache({ expirationTime: 10 }), }); diff --git a/src/common/contracts/core/sybil/index.ts b/src/common/contracts/core/sybil/index.ts index 8e3f17a0..cc495825 100644 --- a/src/common/contracts/core/sybil/index.ts +++ b/src/common/contracts/core/sybil/index.ts @@ -2,9 +2,9 @@ import { MemoryCache } from "@wpdas/naxios"; import { Provider } from "near-api-js/lib/providers"; import { SYBIL_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { FULL_TGAS, ONE_HUNDREDTH_NEAR, TWO_HUNDREDTHS_NEAR } from "@/common/constants"; -import { AccountId, type ByAccountId } from "@/common/types"; +import { AccountId } from "@/common/types"; import { GetHumanScoreInput, HumanScoreResponse } from "./interfaces/is-human"; import { Config } from "./interfaces/lib"; @@ -26,7 +26,7 @@ import { /** * NEAR Contract API */ -export const contractApi = naxiosInstance.contractApi({ +const contractApi = nearProtocolClient.naxiosInstance.contractApi({ contractId: SYBIL_CONTRACT_ACCOUNT_ID, cache: new MemoryCache({ expirationTime: 10 }), // 10 seg }); diff --git a/src/common/viewer/internal/wallet-provider.tsx b/src/common/viewer/internal/wallet-provider.tsx index 0f9e863d..ceba844e 100644 --- a/src/common/viewer/internal/wallet-provider.tsx +++ b/src/common/viewer/internal/wallet-provider.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect } from "react"; -import { nearClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { useWalletContextStore } from "./wallet-context"; @@ -13,8 +13,8 @@ export const WalletProvider: React.FC = ({ children }) => { useWalletContextStore(); const syncWalletState = useCallback(() => { - const isWalletSignedIn = nearClient.walletApi.walletSelector.isSignedIn(); - const walletAccountId = nearClient.walletApi.accountId; + const isWalletSignedIn = nearProtocolClient.walletApi.walletSelector.isSignedIn(); + const walletAccountId = nearProtocolClient.walletApi.accountId; if (isWalletSignedIn !== isSignedIn || walletAccountId !== accountId) { setAccountState({ accountId: walletAccountId, isSignedIn: isWalletSignedIn }); @@ -23,7 +23,7 @@ export const WalletProvider: React.FC = ({ children }) => { useEffect(() => { if (!isReady && error === null) { - nearClient.walletApi + nearProtocolClient.walletApi .initNear() .then(() => registerInit(true)) .catch((error) => { @@ -37,20 +37,20 @@ export const WalletProvider: React.FC = ({ children }) => { if (isReady) { syncWalletState(); - nearClient.walletApi.walletSelector.on("signedIn", syncWalletState); - nearClient.walletApi.walletSelector.on("signedOut", syncWalletState); - nearClient.walletApi.walletSelector.on("accountsChanged", syncWalletState); - nearClient.walletApi.walletSelector.on("networkChanged", syncWalletState); - nearClient.walletApi.walletSelector.on("uriChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("signedIn", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("signedOut", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("accountsChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("networkChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("uriChanged", syncWalletState); } return () => { if (isReady) { - nearClient.walletApi.walletSelector.off("signedIn", syncWalletState); - nearClient.walletApi.walletSelector.off("signedOut", syncWalletState); - nearClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); - nearClient.walletApi.walletSelector.off("networkChanged", syncWalletState); - nearClient.walletApi.walletSelector.off("uriChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("signedIn", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("signedOut", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("networkChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("uriChanged", syncWalletState); } }; }, [syncWalletState, isReady]); diff --git a/src/entities/pot/components/PotPayoutChallenges.tsx b/src/entities/pot/components/PotPayoutChallenges.tsx index bef9395a..84012425 100644 --- a/src/entities/pot/components/PotPayoutChallenges.tsx +++ b/src/entities/pot/components/PotPayoutChallenges.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; import { Pot } from "@/common/api/indexer"; -import { Challenge as ChallengeType, potContractClient } from "@/common/contracts/core/pot"; +import { Challenge as ChallengeType, potContractHooks } from "@/common/contracts/core/pot"; import getTimePassed from "@/common/lib/getTimePassed"; import AdminIcon from "@/common/ui/svg/AdminIcon"; import { CheckedIcon } from "@/common/ui/svg/CheckedIcon"; @@ -14,6 +14,7 @@ import { useGlobalStoreSelector } from "@/store"; import ChallengeResolveModal from "./ChallengeResolveModal"; +// TODO: Refactor export const PotPayoutChallenges = ({ potDetail, setTotalChallenges, @@ -21,6 +22,12 @@ export const PotPayoutChallenges = ({ potDetail?: Pot; setTotalChallenges: (amount: number) => void; }) => { + const { isLoading: isChallengeListLoading, data: challenges } = + potContractHooks.usePayoutChallenges({ + enabled: potDetail?.account !== undefined, + potId: potDetail?.account ?? "noop", + }); + const [tab, setTab] = useState("UNRESOLVED"); const [filteredChallenges, setFilteredChallenges] = useState([]); @@ -30,44 +37,31 @@ export const PotPayoutChallenges = ({ // AccountID (Address) const asDao = actAsDao.toggle && !!actAsDao.defaultAddress; const accountId = asDao ? actAsDao.defaultAddress : _accId; - const [payoutsChallenges, setPayoutsChallenges] = useState([]); const userIsAdminOrGreater = !!potDetail?.admins.find((adm) => adm.id === accountId) || potDetail?.owner.id === accountId; - // Fetch needed data + // TODO: Use `useMemo` for filtered results derived according to `tab` instead! useEffect(() => { - (async () => { - // Get Payouts Challenges for pot - if (potDetail?.account) { - try { - const _payoutsChallenges = await potContractClient.get_payouts_challenges({ - potId: potDetail?.account, - }); - - setPayoutsChallenges(_payoutsChallenges); - setFilteredChallenges(_payoutsChallenges?.filter((c) => !c.resolved)); - setTotalChallenges(_payoutsChallenges?.length); - } catch (e) { - console.error(e); - } - } - })(); - }, [potDetail?.account, accountId, setTotalChallenges]); + if (challenges) { + setFilteredChallenges(challenges.filter((c) => !c.resolved)); + setTotalChallenges(challenges.length); + } + }, [setTotalChallenges, challenges]); const handleSwitchTab = (tab: string) => { setTab(tab); - const filteredChallenges = payoutsChallenges.filter((challenges) => + const filteredChallenges = (challenges ?? []).filter((challenges) => tab === "UNRESOLVED" ? !challenges.resolved : challenges.resolved, ); setFilteredChallenges(filteredChallenges); }; - return !payoutsChallenges ? ( + return !challenges ? ( "Loading..." - ) : payoutsChallenges.length === 0 ? ( + ) : challenges.length === 0 ? ( "" ) : (
@@ -172,7 +166,7 @@ export const PotPayoutChallenges = ({ setAdminModalChallengerId("")} diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index bb238ab6..8d6c5987 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { create, useModal } from "@ebay/nice-modal-react"; -import { nearClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; @@ -41,7 +41,7 @@ export const DonationModal = create((props: DonationModalProps) => { }, [self, setSearchParams]); const onSignInClick = useCallback(() => { - nearClient.walletApi.signInModal(); + nearProtocolClient.walletApi.signInModal(); close(); }, [close]); diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index ffc47db5..b8363621 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; -import { nearClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; @@ -37,7 +37,7 @@ const AuthButton = () => { const viewer = useViewerSession(); const onSignInClick = useCallback(() => { - nearClient.walletApi.signInModal(); + nearProtocolClient.walletApi.signInModal(); }, []); return viewer.hasWalletReady ? ( diff --git a/src/layout/components/user-dropdown.tsx b/src/layout/components/user-dropdown.tsx index d1e8d124..78928198 100644 --- a/src/layout/components/user-dropdown.tsx +++ b/src/layout/components/user-dropdown.tsx @@ -4,7 +4,7 @@ import { LogOut } from "lucide-react"; import Link from "next/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; -import { nearClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { truncate } from "@/common/lib"; import { Button, @@ -31,7 +31,7 @@ export const UserDropdown = () => { }); const logoutHandler = useCallback(() => { - nearClient.walletApi.wallet?.signOut(); + nearProtocolClient.walletApi.wallet?.signOut(); }, []); return ( diff --git a/src/pages/pot/[potId]/votes.tsx b/src/pages/pot/[potId]/votes.tsx index 4c8c43c5..7b7ec57f 100644 --- a/src/pages/pot/[potId]/votes.tsx +++ b/src/pages/pot/[potId]/votes.tsx @@ -10,7 +10,7 @@ import { MdStar, } from "react-icons/md"; -import { nearClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/api/near-protocol"; import { votingContractHooks } from "@/common/contracts/core/voting"; import { isAccountId } from "@/common/lib"; import type { AccountId } from "@/common/types"; @@ -45,7 +45,7 @@ export default function PotVotesTab() { const isDesktop = useMediaQuery("(min-width: 1024px)"); const onSignInClick = useCallback(() => { - nearClient.walletApi.signInModal(); + nearProtocolClient.walletApi.signInModal(); }, []); const [isVotingRuleListVisible, setIsVotingRuleListVisible] = useState(false); From 56f4bf43a70e4644a378cb9cfb85c889f3d03f26 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Wed, 29 Jan 2025 13:55:51 +0000 Subject: [PATCH 64/84] Implement DAO representative session bindings --- src/common/viewer/hooks/session.ts | 36 +++++++++++++----------------- src/common/viewer/types.ts | 11 ++++++--- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/common/viewer/hooks/session.ts b/src/common/viewer/hooks/session.ts index 4a936dd0..c0d003dc 100644 --- a/src/common/viewer/hooks/session.ts +++ b/src/common/viewer/hooks/session.ts @@ -13,45 +13,39 @@ import { ViewerSession } from "../types"; export const useViewerSession = (): ViewerSession => { const wallet = useWalletContextStore(); const { actAsDao } = useGlobalStoreSelector(prop("nav")); - const isDaoRepresentative = actAsDao.toggle && Boolean(actAsDao.defaultAddress); - - const accountId = useMemo(() => { - if (wallet.isSignedIn) { - return isDaoRepresentative ? actAsDao.defaultAddress : wallet.accountId; - } else return undefined; - }, [actAsDao.defaultAddress, isDaoRepresentative, wallet.accountId, wallet.isSignedIn]); - - /** - * Account for edge cases in which the wallet is connected to a mismatching network - * ( e.g. testnet account in mainnet-bound environments ) - */ - const isAccountIdValid = useMemo(() => isAccountId(accountId), [accountId]); + const daoAccountId = actAsDao.defaultAddress; + const isDaoAccountIdValid = useMemo(() => isAccountId(daoAccountId), [daoAccountId]); + const isDaoRepresentative = actAsDao.toggle && isDaoAccountIdValid; const { isLoading: isRegistrationLoading, data: registration } = listsContractHooks.useRegistration({ - enabled: isAccountIdValid, - accountId: accountId ?? "noop", + enabled: wallet.isSignedIn, listId: PUBLIC_GOODS_REGISTRY_LIST_ID, + accountId: (isDaoRepresentative ? daoAccountId : wallet.accountId) ?? "noop", }); - console.log("WALLET", wallet); + console.log("WALLET in SESSION", wallet); return useMemo(() => { - if (wallet.isReady && wallet.isSignedIn && accountId) { + if (wallet.isReady && wallet.isSignedIn && wallet.accountId) { return { hasWalletReady: true, - accountId, + accountId: wallet.accountId, isSignedIn: true, - isDaoRepresentative, isMetadataLoading: isRegistrationLoading, hasRegistrationSubmitted: registration !== undefined, hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, registrationStatus: registration?.status, + + ...(isDaoRepresentative + ? { isDaoRepresentative, daoAccountId } + : { isDaoRepresentative: false, daoAccountId: undefined }), }; } else if (wallet.isReady && !wallet.isSignedIn) { return { hasWalletReady: true, accountId: undefined, + daoAccountId: undefined, isSignedIn: false, isDaoRepresentative: false, isMetadataLoading: false, @@ -63,6 +57,7 @@ export const useViewerSession = (): ViewerSession => { return { hasWalletReady: false, accountId: undefined, + daoAccountId: undefined, isSignedIn: false, isDaoRepresentative: false, isMetadataLoading: false, @@ -72,10 +67,11 @@ export const useViewerSession = (): ViewerSession => { }; } }, [ - accountId, + daoAccountId, isDaoRepresentative, isRegistrationLoading, registration, + wallet.accountId, wallet.isReady, wallet.isSignedIn, ]); diff --git a/src/common/viewer/types.ts b/src/common/viewer/types.ts index 84523a7f..eee34e5a 100644 --- a/src/common/viewer/types.ts +++ b/src/common/viewer/types.ts @@ -1,10 +1,15 @@ import type { RegistrationStatus } from "@/common/contracts/core/lists"; import { AccountId } from "@/common/types"; +type ViewerDaoRepresentativeParams = + | { isDaoRepresentative: false; daoAccountId: undefined } + | { isDaoRepresentative: true; daoAccountId: AccountId }; + export type ViewerSession = | { hasWalletReady: false; accountId: undefined; + daoAccountId: undefined; isSignedIn: false; isDaoRepresentative: false; isMetadataLoading: false; @@ -15,6 +20,7 @@ export type ViewerSession = | { hasWalletReady: true; accountId: undefined; + daoAccountId: undefined; isSignedIn: false; isDaoRepresentative: false; isMetadataLoading: false; @@ -22,13 +28,12 @@ export type ViewerSession = hasRegistrationSubmitted: false; hasRegistrationApproved: false; } - | { + | (ViewerDaoRepresentativeParams & { hasWalletReady: true; accountId: AccountId; isSignedIn: true; - isDaoRepresentative: boolean; isMetadataLoading: boolean; registrationStatus?: RegistrationStatus; hasRegistrationSubmitted: boolean; hasRegistrationApproved: boolean; - }; + }); From 6f4840dc732d446d9361550ed1de9a19e7e0f71e Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Wed, 29 Jan 2025 14:36:27 +0000 Subject: [PATCH 65/84] wip --- src/common/_deprecated/useIsHuman.ts | 32 --- .../contracts/core/sybil-resistance/client.ts | 108 +++++++++++ .../contracts/core/sybil-resistance/hooks.ts | 11 ++ .../contracts/core/sybil-resistance/index.ts | 7 + .../interfaces.ts} | 34 ++++ src/common/contracts/core/sybil/index.ts | 182 ------------------ .../core/sybil/interfaces/is-human.ts | 8 - .../contracts/core/sybil/interfaces/lib.ts | 6 - .../contracts/core/sybil/interfaces/stamps.ts | 19 -- src/common/viewer/hooks/session.ts | 24 ++- src/common/viewer/types.ts | 3 + src/entities/voting-round/hooks/clearance.ts | 11 +- .../voting-round/hooks/voter-profile.ts | 10 +- .../components/DonationSybilWarning.tsx | 6 +- src/features/donation/hooks/forms.ts | 6 - src/layout/profile/components/hero.tsx | 9 +- 16 files changed, 199 insertions(+), 277 deletions(-) delete mode 100644 src/common/_deprecated/useIsHuman.ts create mode 100644 src/common/contracts/core/sybil-resistance/client.ts create mode 100644 src/common/contracts/core/sybil-resistance/hooks.ts create mode 100644 src/common/contracts/core/sybil-resistance/index.ts rename src/common/contracts/core/{sybil/interfaces/providers.ts => sybil-resistance/interfaces.ts} (83%) delete mode 100644 src/common/contracts/core/sybil/index.ts delete mode 100644 src/common/contracts/core/sybil/interfaces/is-human.ts delete mode 100644 src/common/contracts/core/sybil/interfaces/lib.ts delete mode 100644 src/common/contracts/core/sybil/interfaces/stamps.ts diff --git a/src/common/_deprecated/useIsHuman.ts b/src/common/_deprecated/useIsHuman.ts deleted file mode 100644 index 41fa3006..00000000 --- a/src/common/_deprecated/useIsHuman.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect, useState } from "react"; - -import { is_human } from "@/common/contracts/core/sybil"; - -export const useIsHuman = (accountId?: string) => { - const [isHumanVerified, setNadaBotVerified] = useState(false); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchHumanStatus = async () => { - try { - const isHuman = await is_human({ account_id: accountId || "" }); - setNadaBotVerified(isHuman); - setLoading(false); - } catch (error) { - setLoading(false); - setError(error); - } - }; - - if (accountId) { - fetchHumanStatus(); - } - }, [accountId]); - - return { - isHumanVerified, - loading, - error, - }; -}; diff --git a/src/common/contracts/core/sybil-resistance/client.ts b/src/common/contracts/core/sybil-resistance/client.ts new file mode 100644 index 00000000..986756d6 --- /dev/null +++ b/src/common/contracts/core/sybil-resistance/client.ts @@ -0,0 +1,108 @@ +import { Provider } from "near-api-js/lib/providers"; + +import { SYBIL_CONTRACT_ACCOUNT_ID } from "@/common/_config"; +import { nearProtocolClient } from "@/common/api/near-protocol"; +import { FULL_TGAS, ONE_HUNDREDTH_NEAR, TWO_HUNDREDTHS_NEAR } from "@/common/constants"; +import { AccountId } from "@/common/types"; + +import { + ActivateProviderInput, + type Config, + DeactivateProviderInput, + FlagProviderInput, + type GetHumanScoreInput, + type GetStampsForAccountIdInput, + type GetUsersForStampInput, + type HumanScoreResponse, + ProviderExternal, + RegisterProviderInput, + type StampExternal, + UnflagProviderInput, + UpdateProviderInput, +} from "./interfaces"; + +const contractApi = nearProtocolClient.naxiosInstance.contractApi({ + contractId: SYBIL_CONTRACT_ACCOUNT_ID, +}); + +export const get_config = () => contractApi.view<{}, Config>("get_config"); + +export const get_providers = () => contractApi.view("get_providers"); + +export const get_stamps_for_account_id = (args: GetStampsForAccountIdInput) => + contractApi.view("get_stamps_for_account_id", { args }); + +export const get_users_for_stamp = (args: GetUsersForStampInput) => + contractApi.view("get_users_for_stamp", { args }); + +export const get_human_score = (args: GetHumanScoreInput) => + contractApi.view("get_human_score", { args }); + +export const is_human = (args: GetHumanScoreInput): Promise => + contractApi.view("is_human", { args }); + +/** + * Anyone can call this method to register a provider. If caller is admin, provider is automatically activated. + */ +export const register_provider = (args: RegisterProviderInput) => + contractApi.call("register_provider", { + args, + gas: FULL_TGAS, + deposit: ONE_HUNDREDTH_NEAR, + }); + +export type AdminSetDefaultHumanThresholdArgs = { + default_human_threshold: number; +}; + +export const admin_set_default_human_threshold = (args: AdminSetDefaultHumanThresholdArgs) => + contractApi.call("admin_set_default_human_threshold", { + args, + deposit: ONE_HUNDREDTH_NEAR, + }); + +/** + * Add Stamp + * + * Undefined response indicates that user is not verified on target provider + * @param provider_id + * @returns + */ +export const add_stamp = (providerId: string) => + contractApi.call("add_stamp", { + args: { + provider_id: providerId, + }, + gas: FULL_TGAS, + deposit: TWO_HUNDREDTHS_NEAR, + callbackUrl: `${window.location.href}?verifiedProvider=${providerId}`, + }); + +/** + * Update Provider - This method can only be called by the provider's original submitter, or sybil contract owner/admin. + * @param args + * @returns + */ +export const update_provider = (args: UpdateProviderInput) => + contractApi.call("update_provider", { + args, + deposit: ONE_HUNDREDTH_NEAR, + }); + +export const admin_activate_provider = (args: ActivateProviderInput) => + contractApi.call("admin_activate_provider", { + args, + deposit: ONE_HUNDREDTH_NEAR, + }); + +export const admin_deactivate_provider = (args: DeactivateProviderInput) => + contractApi.call("admin_deactivate_provider", { + args, + deposit: ONE_HUNDREDTH_NEAR, + }); + +export const admin_flag_provider = (args: FlagProviderInput) => + contractApi.call("admin_flag_provider", { args }); + +export const admin_unflag_provider = (args: UnflagProviderInput) => + contractApi.call("admin_unflag_provider", { args }); diff --git a/src/common/contracts/core/sybil-resistance/hooks.ts b/src/common/contracts/core/sybil-resistance/hooks.ts new file mode 100644 index 00000000..d1f8be37 --- /dev/null +++ b/src/common/contracts/core/sybil-resistance/hooks.ts @@ -0,0 +1,11 @@ +import useSWR from "swr"; + +import { IS_CLIENT } from "@/common/constants"; +import type { ByAccountId, ConditionalActivation } from "@/common/types"; + +import * as contractClient from "./client"; + +export const useIsHuman = ({ enabled = true, accountId }: ByAccountId & ConditionalActivation) => + useSWR(["useIsHuman", accountId], ([_queryKeyHead, accountIdKey]) => + !enabled || !IS_CLIENT ? undefined : contractClient.is_human({ account_id: accountIdKey }), + ); diff --git a/src/common/contracts/core/sybil-resistance/index.ts b/src/common/contracts/core/sybil-resistance/index.ts new file mode 100644 index 00000000..6917377c --- /dev/null +++ b/src/common/contracts/core/sybil-resistance/index.ts @@ -0,0 +1,7 @@ +import * as sybilResistanceContractClient from "./client"; +import * as sybilResistanceContractHooks from "./hooks"; + +export type * from "./hooks"; +export * from "./interfaces"; + +export { sybilResistanceContractClient, sybilResistanceContractHooks }; diff --git a/src/common/contracts/core/sybil/interfaces/providers.ts b/src/common/contracts/core/sybil-resistance/interfaces.ts similarity index 83% rename from src/common/contracts/core/sybil/interfaces/providers.ts rename to src/common/contracts/core/sybil-resistance/interfaces.ts index 911ff518..1a39260b 100644 --- a/src/common/contracts/core/sybil/interfaces/providers.ts +++ b/src/common/contracts/core/sybil-resistance/interfaces.ts @@ -1,3 +1,19 @@ +export interface Config { + owner: string; + admins: string[]; + default_provider_ids: string[]; + default_human_threshold: number; +} + +export interface GetHumanScoreInput { + account_id: string; +} + +export interface HumanScoreResponse { + is_human: boolean; + score: number; +} + export enum ProviderStatus { Pending = "Pending", Active = "Active", @@ -107,3 +123,21 @@ export interface DeactivateProviderInput { export type FlagProviderInput = DeactivateProviderInput; export type UnflagProviderInput = DeactivateProviderInput; + +export interface StampExternal { + user_id: string; + provider: ProviderExternal; + validated_at_ms: number; +} + +export interface GetStampsForAccountIdInput { + account_id: string; + from_index?: number; + limit?: number; +} + +export type GetUsersForStampInput = { + provider_id: string; + from_index?: number; + limit?: number; +}; diff --git a/src/common/contracts/core/sybil/index.ts b/src/common/contracts/core/sybil/index.ts deleted file mode 100644 index cc495825..00000000 --- a/src/common/contracts/core/sybil/index.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { MemoryCache } from "@wpdas/naxios"; -import { Provider } from "near-api-js/lib/providers"; - -import { SYBIL_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { nearProtocolClient } from "@/common/api/near-protocol"; -import { FULL_TGAS, ONE_HUNDREDTH_NEAR, TWO_HUNDREDTHS_NEAR } from "@/common/constants"; -import { AccountId } from "@/common/types"; - -import { GetHumanScoreInput, HumanScoreResponse } from "./interfaces/is-human"; -import { Config } from "./interfaces/lib"; -import { - ActivateProviderInput, - DeactivateProviderInput, - FlagProviderInput, - ProviderExternal, - RegisterProviderInput, - UnflagProviderInput, - UpdateProviderInput, -} from "./interfaces/providers"; -import { - GetStampsForAccountIdInput, - GetUsersForStampInput, - StampExternal, -} from "./interfaces/stamps"; - -/** - * NEAR Contract API - */ -const contractApi = nearProtocolClient.naxiosInstance.contractApi({ - contractId: SYBIL_CONTRACT_ACCOUNT_ID, - cache: new MemoryCache({ expirationTime: 10 }), // 10 seg -}); - -// READ METHODS - -/** - * Get Config - */ -export const getConfig = () => contractApi.view<{}, Config>("get_config"); - -/** - * Get Providers - * @returns - */ -export const getProviders = () => - contractApi.view("get_providers", undefined, { - useCache: true, - }); - -/** - * Get Stamps for Account Id - * @returns - */ -export const getStampsForAccountId = (args: GetStampsForAccountIdInput) => - contractApi.view( - "get_stamps_for_account_id", - { - args, - }, - { useCache: true }, - ); - -/** - * Get Users for Stamps - * @returns - */ -export const getUsersForStamp = (args: GetUsersForStampInput) => - contractApi.view("get_users_for_stamp", { - args, - }); - -/** - * Get Human Score - * @returns - */ -export const getHumanScore = (args: GetHumanScoreInput) => - contractApi.view("get_human_score", { - args, - }); - -/** - * Get if address is human - * @returns - */ -export const is_human = (args: GetHumanScoreInput): Promise => - contractApi.view("is_human", { - args, - }); - -// WRITE METHODS -/** - * Anyone can call this method to register a provider. If caller is admin, provider is automatically activated. - */ -export const registerProvider = (args: RegisterProviderInput) => - contractApi.call("register_provider", { - args, - gas: FULL_TGAS, - deposit: ONE_HUNDREDTH_NEAR, - }); - -/** - * Set default human threshold - * @param default_human_threshold - * @returns - */ -export const adminSetDefaultHumanThreshold = (defaultHumanThreshold: number) => - contractApi.call("admin_set_default_human_threshold", { - args: { - default_human_threshold: defaultHumanThreshold, - }, - deposit: ONE_HUNDREDTH_NEAR, - }); - -/** - * Add Stamp - * - * Undefined response indicates that user is not verified on target provider - * @param provider_id - * @returns - */ -export const addStamp = (providerId: string) => - contractApi.call("add_stamp", { - args: { - provider_id: providerId, - }, - gas: FULL_TGAS, - deposit: TWO_HUNDREDTHS_NEAR, - callbackUrl: `${window.location.href}?verifiedProvider=${providerId}`, - }); - -/** - * Update Provider - This method can only be called by the provider's original submitter, or sybil contract owner/admin. - * @param args - * @returns - */ -export const updateProvider = (args: UpdateProviderInput) => - contractApi.call("update_provider", { - args, - deposit: ONE_HUNDREDTH_NEAR, - }); - -/** - * Activate Provider - * @param args - * @returns - */ -export const adminActivateProvider = (args: ActivateProviderInput) => - contractApi.call("admin_activate_provider", { - args, - deposit: ONE_HUNDREDTH_NEAR, - }); - -/** - * Deactivate Provider - * @param args - * @returns - */ -export const adminDeactivateProvider = (args: DeactivateProviderInput) => - contractApi.call("admin_deactivate_provider", { - args, - deposit: ONE_HUNDREDTH_NEAR, - }); - -/** - * Flag Provider - * @param args - * @returns - */ -export const adminFlagProvider = (args: FlagProviderInput) => - contractApi.call("admin_flag_provider", { - args, - }); - -/** - * Unflag Provider - * @param args - * @returns - */ -export const adminUnflagProvider = (args: UnflagProviderInput) => - contractApi.call("admin_unflag_provider", { - args, - }); diff --git a/src/common/contracts/core/sybil/interfaces/is-human.ts b/src/common/contracts/core/sybil/interfaces/is-human.ts deleted file mode 100644 index 4b5cd06e..00000000 --- a/src/common/contracts/core/sybil/interfaces/is-human.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface GetHumanScoreInput { - account_id: string; -} - -export interface HumanScoreResponse { - is_human: boolean; - score: number; -} diff --git a/src/common/contracts/core/sybil/interfaces/lib.ts b/src/common/contracts/core/sybil/interfaces/lib.ts deleted file mode 100644 index 4588a670..00000000 --- a/src/common/contracts/core/sybil/interfaces/lib.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Config { - owner: string; - admins: string[]; - default_provider_ids: string[]; - default_human_threshold: number; -} diff --git a/src/common/contracts/core/sybil/interfaces/stamps.ts b/src/common/contracts/core/sybil/interfaces/stamps.ts deleted file mode 100644 index 995d85ad..00000000 --- a/src/common/contracts/core/sybil/interfaces/stamps.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ProviderExternal } from "./providers"; - -export interface StampExternal { - user_id: string; - provider: ProviderExternal; - validated_at_ms: number; -} - -export interface GetStampsForAccountIdInput { - account_id: string; - from_index?: number; - limit?: number; -} - -export type GetUsersForStampInput = { - provider_id: string; - from_index?: number; - limit?: number; -}; diff --git a/src/common/viewer/hooks/session.ts b/src/common/viewer/hooks/session.ts index c0d003dc..ac624aa1 100644 --- a/src/common/viewer/hooks/session.ts +++ b/src/common/viewer/hooks/session.ts @@ -4,6 +4,7 @@ import { prop } from "remeda"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { RegistrationStatus, listsContractHooks } from "@/common/contracts/core/lists"; +import { sybilResistanceContractHooks } from "@/common/contracts/core/sybil-resistance"; import { isAccountId } from "@/common/lib"; import { useGlobalStoreSelector } from "@/store"; @@ -17,6 +18,12 @@ export const useViewerSession = (): ViewerSession => { const isDaoAccountIdValid = useMemo(() => isAccountId(daoAccountId), [daoAccountId]); const isDaoRepresentative = actAsDao.toggle && isDaoAccountIdValid; + const { isLoading: isHumanVerificationStatusLoading, data: isHuman } = + sybilResistanceContractHooks.useIsHuman({ + enabled: wallet.isSignedIn, + accountId: wallet.accountId ?? "noop", + }); + const { isLoading: isRegistrationLoading, data: registration } = listsContractHooks.useRegistration({ enabled: wallet.isSignedIn, @@ -24,6 +31,8 @@ export const useViewerSession = (): ViewerSession => { accountId: (isDaoRepresentative ? daoAccountId : wallet.accountId) ?? "noop", }); + const isMetadataLoading = isHumanVerificationStatusLoading || isRegistrationLoading; + console.log("WALLET in SESSION", wallet); return useMemo(() => { @@ -32,14 +41,16 @@ export const useViewerSession = (): ViewerSession => { hasWalletReady: true, accountId: wallet.accountId, isSignedIn: true, - isMetadataLoading: isRegistrationLoading, - hasRegistrationSubmitted: registration !== undefined, - hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, - registrationStatus: registration?.status, ...(isDaoRepresentative ? { isDaoRepresentative, daoAccountId } : { isDaoRepresentative: false, daoAccountId: undefined }), + + isHuman: isHuman ?? false, + isMetadataLoading, + hasRegistrationSubmitted: registration !== undefined, + hasRegistrationApproved: registration?.status === RegistrationStatus.Approved, + registrationStatus: registration?.status, }; } else if (wallet.isReady && !wallet.isSignedIn) { return { @@ -48,6 +59,7 @@ export const useViewerSession = (): ViewerSession => { daoAccountId: undefined, isSignedIn: false, isDaoRepresentative: false, + isHuman: false, isMetadataLoading: false, hasRegistrationSubmitted: false, hasRegistrationApproved: false, @@ -60,6 +72,7 @@ export const useViewerSession = (): ViewerSession => { daoAccountId: undefined, isSignedIn: false, isDaoRepresentative: false, + isHuman: false, isMetadataLoading: false, hasRegistrationSubmitted: false, hasRegistrationApproved: false, @@ -69,7 +82,8 @@ export const useViewerSession = (): ViewerSession => { }, [ daoAccountId, isDaoRepresentative, - isRegistrationLoading, + isHuman, + isMetadataLoading, registration, wallet.accountId, wallet.isReady, diff --git a/src/common/viewer/types.ts b/src/common/viewer/types.ts index eee34e5a..524cad80 100644 --- a/src/common/viewer/types.ts +++ b/src/common/viewer/types.ts @@ -12,6 +12,7 @@ export type ViewerSession = daoAccountId: undefined; isSignedIn: false; isDaoRepresentative: false; + isHuman: false; isMetadataLoading: false; registrationStatus: undefined; hasRegistrationSubmitted: false; @@ -23,6 +24,7 @@ export type ViewerSession = daoAccountId: undefined; isSignedIn: false; isDaoRepresentative: false; + isHuman: false; isMetadataLoading: false; registrationStatus: undefined; hasRegistrationSubmitted: false; @@ -32,6 +34,7 @@ export type ViewerSession = hasWalletReady: true; accountId: AccountId; isSignedIn: true; + isHuman: boolean; isMetadataLoading: boolean; registrationStatus?: RegistrationStatus; hasRegistrationSubmitted: boolean; diff --git a/src/entities/voting-round/hooks/clearance.ts b/src/entities/voting-round/hooks/clearance.ts index e2afed8c..ea52db55 100644 --- a/src/entities/voting-round/hooks/clearance.ts +++ b/src/entities/voting-round/hooks/clearance.ts @@ -2,7 +2,6 @@ import { useMemo } from "react"; import { prop } from "remeda"; -import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { ClearanceCheckResult } from "@/common/types"; import { useViewerSession } from "@/common/viewer"; @@ -12,19 +11,19 @@ import { useViewerSession } from "@/common/viewer"; * as it's built for the mpDAO milestone. */ export const useVotingRoundSessionClearance = (): ClearanceCheckResult => { - const { accountId, hasRegistrationApproved } = useViewerSession(); - const { isHumanVerified: isHuman } = useIsHuman(accountId); + const viewer = useViewerSession(); return useMemo(() => { const requirements = [ - { title: "Must have an account on POTLOCK.", isSatisfied: hasRegistrationApproved }, - { title: "Must have human verification.", isSatisfied: isHuman }, + { title: "Must have an account on POTLOCK.", isSatisfied: viewer.hasRegistrationApproved }, + { title: "Must have human verification.", isSatisfied: viewer.isHuman }, ]; return { + isLoading: viewer.isMetadataLoading, requirements, isEveryRequirementSatisfied: requirements.every(prop("isSatisfied")), error: null, }; - }, [isHuman, hasRegistrationApproved]); + }, [viewer.hasRegistrationApproved, viewer.isHuman, viewer.isMetadataLoading]); }; diff --git a/src/entities/voting-round/hooks/voter-profile.ts b/src/entities/voting-round/hooks/voter-profile.ts index e2be6132..886a846b 100644 --- a/src/entities/voting-round/hooks/voter-profile.ts +++ b/src/entities/voting-round/hooks/voter-profile.ts @@ -2,8 +2,8 @@ import { useMemo } from "react"; import { Big } from "big.js"; -import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { indexer } from "@/common/api/indexer"; +import { sybilResistanceContractHooks } from "@/common/contracts/core/sybil-resistance"; import { METAPOOL_MPDAO_VOTING_POWER_DECIMALS } from "@/common/contracts/metapool"; import { indivisibleUnitsToBigNum } from "@/common/lib"; import { ByAccountId, type ConditionalActivation, TokenId } from "@/common/types"; @@ -22,11 +22,11 @@ export type VoterProfileInputs = ByAccountId & { * Though it is in the process of being refactored, the job is not entirely done yet. */ export const useVoterProfile = ({ + enabled = true, accountId, stakingContractAccountId, - enabled = true, }: VoterProfileInputs & ConditionalActivation): VoterProfile => { - const { isHumanVerified } = useIsHuman(accountId); + const { data: isHuman } = sybilResistanceContractHooks.useIsHuman({ accountId }); const { data: voterInfo } = indexer.useMpdaoVoter({ enabled, accountId }); const tokenId = useMemo( @@ -42,7 +42,7 @@ export const useVoterProfile = ({ return useMemo( () => ({ accountId, - isHumanVerified, + isHumanVerified: isHuman ?? false, stakingTokenBalance: voterInfo?.voter_data.staking_token_balance && stakingToken @@ -63,7 +63,7 @@ export const useVoterProfile = ({ [ accountId, - isHumanVerified, + isHuman, stakingToken, voterInfo?.voter_data.locking_positions, voterInfo?.voter_data.staking_token_balance, diff --git a/src/features/donation/components/DonationSybilWarning.tsx b/src/features/donation/components/DonationSybilWarning.tsx index 0132f55e..1c8618f8 100644 --- a/src/features/donation/components/DonationSybilWarning.tsx +++ b/src/features/donation/components/DonationSybilWarning.tsx @@ -1,7 +1,6 @@ import { useMemo } from "react"; import { SYBIL_APP_LINK_URL } from "@/common/_config"; -import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { ByPotId, indexer } from "@/common/api/indexer"; import { Alert, AlertDescription, AlertTitle, Button } from "@/common/ui/components"; import { WarningIcon } from "@/common/ui/svg"; @@ -18,12 +17,11 @@ export const DonationSybilWarning: React.FC = ({ classNames, }) => { const viewer = useViewerSession(); - const { isHumanVerified: isDonorNadabotVerified } = useIsHuman(viewer.accountId); const { data: pot } = indexer.usePot({ potId }); const isVisible = useMemo( - () => typeof pot?.sybil_wrapper_provider === "string" && !isDonorNadabotVerified, - [isDonorNadabotVerified, pot?.sybil_wrapper_provider], + () => typeof pot?.sybil_wrapper_provider === "string" && !viewer.isHuman, + [pot?.sybil_wrapper_provider, viewer.isHuman], ); return !isVisible ? null : ( diff --git a/src/features/donation/hooks/forms.ts b/src/features/donation/hooks/forms.ts index 4b974d3d..33511656 100644 --- a/src/features/donation/hooks/forms.ts +++ b/src/features/donation/hooks/forms.ts @@ -6,11 +6,9 @@ import { entries } from "remeda"; import { Temporal } from "temporal-polyfill"; import { ZodError } from "zod"; -import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PotApplicationStatus, indexer } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; import { oldToRecent } from "@/common/lib"; -import { useViewerSession } from "@/common/viewer"; import { dispatch } from "@/store"; import { DONATION_MIN_NEAR_AMOUNT, DONATION_MIN_NEAR_AMOUNT_ERROR } from "../constants"; @@ -28,7 +26,6 @@ export type DonationFormParams = DonationAllocationKey & { export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormParams) => { const now = Temporal.Now.instant(); - const viewer = useViewerSession(); const isSingleProjectDonation = "accountId" in params; const isPotDonation = "potId" in params; const isListDonation = "listId" in params; @@ -141,8 +138,6 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa const isDisabled = !hasChanges || !self.formState.isValid || self.formState.isSubmitting; - const isSenderHumanVerified = useIsHuman(viewer.accountId); - const minAmountError = !isDonationAmountSufficient({ amount, tokenId }) && hasChanges ? DONATION_MIN_NEAR_AMOUNT_ERROR @@ -197,7 +192,6 @@ export const useDonationForm = ({ referrerAccountId, ...params }: DonationFormPa defaultValues, hasChanges, isDisabled, - isSenderHumanVerified, onSubmit: self.handleSubmit(onSubmit), matchingPots, minAmountError, diff --git a/src/layout/profile/components/hero.tsx b/src/layout/profile/components/hero.tsx index 7cc3bab0..455b1ea4 100644 --- a/src/layout/profile/components/hero.tsx +++ b/src/layout/profile/components/hero.tsx @@ -1,6 +1,6 @@ -import { useIsHuman } from "@/common/_deprecated/useIsHuman"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { listsContractHooks } from "@/common/contracts/core/lists"; +import { sybilResistanceContractHooks } from "@/common/contracts/core/sybil-resistance"; import type { ByAccountId } from "@/common/types"; import { cn } from "@/common/ui/utils"; import { @@ -13,7 +13,8 @@ import { listRegistrationStatusIcons } from "@/entities/list"; export type ProfileLayoutHeroProps = ByAccountId & {}; export const ProfileLayoutHero: React.FC = ({ accountId }) => { - const { isHumanVerified } = useIsHuman(accountId); + const { isLoading: isHumanVerificationStatusLoading, data: isHuman } = + sybilResistanceContractHooks.useIsHuman({ accountId }); // TODO: For optimization, request and use an indexer endpoint for list registration by specified accountId and listId // TODO: Also implement error and loading status handling @@ -46,7 +47,7 @@ export const ProfileLayoutHero: React.FC = ({ accountId "relative z-[1] flex -translate-y-5 translate-x-[-25px] items-center gap-2 md:gap-6", )} > - {pgRegistryRegistration || isHumanVerified ? ( + {pgRegistryRegistration || isHuman ? ( <> {pgRegistryRegistration && (
= ({ accountId
)} - {pgRegistryRegistration === undefined && isHumanVerified && ( + {pgRegistryRegistration === undefined && isHuman && (
Date: Wed, 29 Jan 2025 14:43:52 +0000 Subject: [PATCH 66/84] wip --- src/common/api/near-social-indexer/queries.ts | 2 +- src/common/contracts/metapool/liquid-staking/index.ts | 4 ++-- src/common/contracts/ref-finance/ref-exchange/index.ts | 6 +++--- src/common/contracts/{social => social-db}/client.ts | 0 src/common/contracts/{social => social-db}/hooks.ts | 0 src/common/contracts/{social => social-db}/index.ts | 0 .../contracts/{social => social-db}/interfaces.ts | 0 src/common/contracts/tokens/ft/index.ts | 7 ------- src/common/contracts/tokens/{ft => fungible}/client.ts | 0 src/common/contracts/tokens/{ft => fungible}/hooks.ts | 6 +++--- src/common/contracts/tokens/fungible/index.ts | 7 +++++++ .../contracts/tokens/{ft => fungible}/interfaces.ts | 0 src/common/contracts/tokens/index.ts | 2 +- src/common/services/images.ts | 2 +- .../_shared/account/components/AccountFollowButton.tsx | 2 +- src/entities/_shared/account/hooks/social-profile.ts | 2 +- src/entities/_shared/account/types.ts | 2 +- src/entities/_shared/token/hooks.ts | 10 +++++----- src/entities/post/components/PostEditor.tsx | 2 +- src/entities/project/components/Team.tsx | 2 +- .../project/utils/getTagsFromSocialProfileData.ts | 2 +- src/entities/voting-round/model/results.ts | 6 ++++-- src/features/donation/models/effects.ts | 2 +- src/features/profile-setup/models/deprecated.ts | 2 +- src/features/profile-setup/models/effects.ts | 2 +- src/layout/profile/components/SmartContract.tsx | 2 +- src/layout/profile/components/github-repos.tsx | 2 +- src/pages/profile/[accountId]/feed.tsx | 2 +- src/pages/profile/[accountId]/funding-raised.tsx | 2 +- 29 files changed, 40 insertions(+), 38 deletions(-) rename src/common/contracts/{social => social-db}/client.ts (100%) rename src/common/contracts/{social => social-db}/hooks.ts (100%) rename src/common/contracts/{social => social-db}/index.ts (100%) rename src/common/contracts/{social => social-db}/interfaces.ts (100%) delete mode 100644 src/common/contracts/tokens/ft/index.ts rename src/common/contracts/tokens/{ft => fungible}/client.ts (100%) rename src/common/contracts/tokens/{ft => fungible}/hooks.ts (75%) create mode 100644 src/common/contracts/tokens/fungible/index.ts rename src/common/contracts/tokens/{ft => fungible}/interfaces.ts (100%) diff --git a/src/common/api/near-social-indexer/queries.ts b/src/common/api/near-social-indexer/queries.ts index f9ffbdf7..a5fdf960 100644 --- a/src/common/api/near-social-indexer/queries.ts +++ b/src/common/api/near-social-indexer/queries.ts @@ -2,7 +2,7 @@ import { FeedsResult, IndexPostResultItem, PostContent, -} from "@/common/contracts/social/interfaces"; +} from "@/common/contracts/social-db/interfaces"; import { nearSocialClient } from "./client"; diff --git a/src/common/contracts/metapool/liquid-staking/index.ts b/src/common/contracts/metapool/liquid-staking/index.ts index da2e87da..bf7ce19d 100644 --- a/src/common/contracts/metapool/liquid-staking/index.ts +++ b/src/common/contracts/metapool/liquid-staking/index.ts @@ -1,3 +1,3 @@ -import * as liquidStakingClient from "./client"; +import * as metapoolLiquidStakingClient from "./client"; -export { liquidStakingClient }; +export { metapoolLiquidStakingClient }; diff --git a/src/common/contracts/ref-finance/ref-exchange/index.ts b/src/common/contracts/ref-finance/ref-exchange/index.ts index 2e4debf7..4d76d543 100644 --- a/src/common/contracts/ref-finance/ref-exchange/index.ts +++ b/src/common/contracts/ref-finance/ref-exchange/index.ts @@ -1,6 +1,6 @@ -import * as refExchangeClient from "./client"; -import * as refExchangeHooks from "./hooks"; +import * as refExchangeContractClient from "./client"; +import * as refExchangeContractHooks from "./hooks"; export * from "./interfaces"; -export { refExchangeClient, refExchangeHooks }; +export { refExchangeContractClient, refExchangeContractHooks }; diff --git a/src/common/contracts/social/client.ts b/src/common/contracts/social-db/client.ts similarity index 100% rename from src/common/contracts/social/client.ts rename to src/common/contracts/social-db/client.ts diff --git a/src/common/contracts/social/hooks.ts b/src/common/contracts/social-db/hooks.ts similarity index 100% rename from src/common/contracts/social/hooks.ts rename to src/common/contracts/social-db/hooks.ts diff --git a/src/common/contracts/social/index.ts b/src/common/contracts/social-db/index.ts similarity index 100% rename from src/common/contracts/social/index.ts rename to src/common/contracts/social-db/index.ts diff --git a/src/common/contracts/social/interfaces.ts b/src/common/contracts/social-db/interfaces.ts similarity index 100% rename from src/common/contracts/social/interfaces.ts rename to src/common/contracts/social-db/interfaces.ts diff --git a/src/common/contracts/tokens/ft/index.ts b/src/common/contracts/tokens/ft/index.ts deleted file mode 100644 index 2e513f12..00000000 --- a/src/common/contracts/tokens/ft/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as ftClient from "./client"; -import * as ftHooks from "./hooks"; - -export type * from "./hooks"; -export * from "./interfaces"; - -export { ftClient, ftHooks }; diff --git a/src/common/contracts/tokens/ft/client.ts b/src/common/contracts/tokens/fungible/client.ts similarity index 100% rename from src/common/contracts/tokens/ft/client.ts rename to src/common/contracts/tokens/fungible/client.ts diff --git a/src/common/contracts/tokens/ft/hooks.ts b/src/common/contracts/tokens/fungible/hooks.ts similarity index 75% rename from src/common/contracts/tokens/ft/hooks.ts rename to src/common/contracts/tokens/fungible/hooks.ts index ab18a309..76a1268e 100644 --- a/src/common/contracts/tokens/ft/hooks.ts +++ b/src/common/contracts/tokens/fungible/hooks.ts @@ -3,13 +3,13 @@ import useSWR from "swr"; import { IS_CLIENT } from "@/common/constants"; import type { ByAccountId, ByTokenId, WithDisabled } from "@/common/types"; -import * as ftClient from "./client"; +import * as ftContractClient from "./client"; // TODO: Use conventional `enabled` instead of `disabled` export const useFtMetadata = ({ disabled = false, ...params }: ByTokenId & WithDisabled) => useSWR( () => (disabled || !IS_CLIENT ? null : ["ft_metadata", params.tokenId]), - ([_queryKeyHead, tokenId]) => ftClient.ft_metadata({ tokenId }).catch(() => undefined), + ([_queryKeyHead, tokenId]) => ftContractClient.ft_metadata({ tokenId }).catch(() => undefined), ); // TODO: Use conventional `enabled` instead of `disabled` @@ -21,5 +21,5 @@ export const useFtBalanceOf = ({ () => (disabled || !IS_CLIENT ? null : ["ft_balance_of", params.accountId, params.tokenId]), ([_queryKeyHead, accountId, tokenId]) => - ftClient.ft_balance_of({ accountId, tokenId }).catch(() => undefined), + ftContractClient.ft_balance_of({ accountId, tokenId }).catch(() => undefined), ); diff --git a/src/common/contracts/tokens/fungible/index.ts b/src/common/contracts/tokens/fungible/index.ts new file mode 100644 index 00000000..635d72de --- /dev/null +++ b/src/common/contracts/tokens/fungible/index.ts @@ -0,0 +1,7 @@ +import * as ftContractClient from "./client"; +import * as ftContractHooks from "./hooks"; + +export type * from "./hooks"; +export * from "./interfaces"; + +export { ftContractClient, ftContractHooks }; diff --git a/src/common/contracts/tokens/ft/interfaces.ts b/src/common/contracts/tokens/fungible/interfaces.ts similarity index 100% rename from src/common/contracts/tokens/ft/interfaces.ts rename to src/common/contracts/tokens/fungible/interfaces.ts diff --git a/src/common/contracts/tokens/index.ts b/src/common/contracts/tokens/index.ts index 2ea43e35..1eb3515a 100644 --- a/src/common/contracts/tokens/index.ts +++ b/src/common/contracts/tokens/index.ts @@ -1 +1 @@ -export * from "./ft"; +export * from "./fungible"; diff --git a/src/common/services/images.ts b/src/common/services/images.ts index f378747a..2a843799 100644 --- a/src/common/services/images.ts +++ b/src/common/services/images.ts @@ -1,5 +1,5 @@ import { naxiosInstance } from "@/common/api/near-protocol/client"; -import { Image, socialDbContractClient } from "@/common/contracts/social"; +import { Image, socialDbContractClient } from "@/common/contracts/social-db"; type Props = { accountId?: string; diff --git a/src/entities/_shared/account/components/AccountFollowButton.tsx b/src/entities/_shared/account/components/AccountFollowButton.tsx index ab01014f..8553228c 100644 --- a/src/entities/_shared/account/components/AccountFollowButton.tsx +++ b/src/entities/_shared/account/components/AccountFollowButton.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { nearSocialIndexerHooks } from "@/common/api/near-social-indexer"; -import { socialDbContractClient } from "@/common/contracts/social"; +import { socialDbContractClient } from "@/common/contracts/social-db"; import type { ByAccountId } from "@/common/types"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; diff --git a/src/entities/_shared/account/hooks/social-profile.ts b/src/entities/_shared/account/hooks/social-profile.ts index e15ea8e2..c5c09a23 100644 --- a/src/entities/_shared/account/hooks/social-profile.ts +++ b/src/entities/_shared/account/hooks/social-profile.ts @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import { socialDbContractHooks } from "@/common/contracts/social"; +import { socialDbContractHooks } from "@/common/contracts/social-db"; import type { ByAccountId, ConditionalActivation } from "@/common/types"; import { diff --git a/src/entities/_shared/account/types.ts b/src/entities/_shared/account/types.ts index 30ee611d..54f9b26f 100644 --- a/src/entities/_shared/account/types.ts +++ b/src/entities/_shared/account/types.ts @@ -1,4 +1,4 @@ -import type { ProfileLinktree } from "@/common/contracts/social"; +import type { ProfileLinktree } from "@/common/contracts/social-db"; import { ByAccountId, ByRegistrationId } from "@/common/types"; export type AccountPower = { diff --git a/src/entities/_shared/token/hooks.ts b/src/entities/_shared/token/hooks.ts index beb2dd48..b98b5d46 100644 --- a/src/entities/_shared/token/hooks.ts +++ b/src/entities/_shared/token/hooks.ts @@ -6,8 +6,8 @@ import { coingeckoHooks } from "@/common/api/coingecko"; import { intearTokenIndexerHooks } from "@/common/api/intear-token-indexer"; import { nearHooks } from "@/common/api/near-protocol"; import { NATIVE_TOKEN_ID, PLATFORM_LISTED_TOKEN_IDS } from "@/common/constants"; -import { refExchangeHooks } from "@/common/contracts/ref-finance"; -import { ftHooks } from "@/common/contracts/tokens"; +import { refExchangeContractHooks } from "@/common/contracts/ref-finance"; +import { ftContractHooks } from "@/common/contracts/tokens"; import { indivisibleUnitsToBigNum, indivisibleUnitsToFloat, isAccountId } from "@/common/lib"; import { formatWithCommas } from "@/common/lib/formatWithCommas"; import type { AccountId, ByTokenId, ConditionalActivation } from "@/common/types"; @@ -31,7 +31,7 @@ export const useTokenUsdDisplayValue = ({ }; export const useTokenAllowlist = () => { - const { data: refFinanceTokenAllowlist } = refExchangeHooks.useWhitelistedTokens(); + const { data: refFinanceTokenAllowlist } = refExchangeContractHooks.useWhitelistedTokens(); return useMemo( () => ({ @@ -84,7 +84,7 @@ export const useToken = ({ isLoading: isFtMetadataLoading, data: ftMetadata, error: ftMetadataError, - } = ftHooks.useFtMetadata({ + } = ftContractHooks.useFtMetadata({ disabled: !enabled || !isValidFtContractAccountId, tokenId, }); @@ -102,7 +102,7 @@ export const useToken = ({ isLoading: isFtBalanceLoading, data: ftBalance, error: ftBalanceError, - } = ftHooks.useFtBalanceOf({ + } = ftContractHooks.useFtBalanceOf({ disabled: balanceCheckAccountId === undefined || !isValidFtContractAccountId, accountId: balanceCheckAccountId as AccountId, tokenId, diff --git a/src/entities/post/components/PostEditor.tsx b/src/entities/post/components/PostEditor.tsx index 96f8adb7..1c24df57 100644 --- a/src/entities/post/components/PostEditor.tsx +++ b/src/entities/post/components/PostEditor.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { LazyLoadImage } from "react-lazy-load-image-component"; -import { socialDbContractClient } from "@/common/contracts/social"; +import { socialDbContractClient } from "@/common/contracts/social-db"; import { AccountId } from "@/common/types"; import { Button, Textarea } from "@/common/ui/components"; import { useViewerSession } from "@/common/viewer"; diff --git a/src/entities/project/components/Team.tsx b/src/entities/project/components/Team.tsx index eeb896a5..b4d0b4ac 100644 --- a/src/entities/project/components/Team.tsx +++ b/src/entities/project/components/Team.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; -import { NEARSocialUserProfile } from "@/common/contracts/social"; +import { NEARSocialUserProfile } from "@/common/contracts/social-db"; import type { AccountId } from "@/common/types"; import { useAccountSocialProfile } from "@/entities/_shared/account"; import { rootPathnames } from "@/pathnames"; diff --git a/src/entities/project/utils/getTagsFromSocialProfileData.ts b/src/entities/project/utils/getTagsFromSocialProfileData.ts index dde13535..3dc5578a 100644 --- a/src/entities/project/utils/getTagsFromSocialProfileData.ts +++ b/src/entities/project/utils/getTagsFromSocialProfileData.ts @@ -1,4 +1,4 @@ -import { NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social"; +import { NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social-db"; const isEmptyObject = (obj: object): boolean => obj && Object.keys(obj).length === 0; diff --git a/src/entities/voting-round/model/results.ts b/src/entities/voting-round/model/results.ts index a36dc055..a7fd8ec2 100644 --- a/src/entities/voting-round/model/results.ts +++ b/src/entities/voting-round/model/results.ts @@ -5,7 +5,7 @@ import { persist } from "zustand/middleware"; import { type MpdaoVoterItem } from "@/common/api/indexer"; import { AccountId, type ElectionId, Vote } from "@/common/contracts/core/voting"; -import { ftClient } from "@/common/contracts/tokens/ft"; +import { ftContractClient } from "@/common/contracts/tokens/fungible"; import { indivisibleUnitsToBigNum } from "@/common/lib"; import type { VoterProfile, VotingMechanismConfig, VotingRoundWinner } from "../types"; @@ -45,7 +45,9 @@ export const useVotingRoundResultsStore = create()( matchingPoolBalance, }) => { const stakingTokenMetadata = mechanismConfig.stakingContractAccountId - ? await ftClient.ft_metadata({ tokenId: mechanismConfig.stakingContractAccountId }) + ? await ftContractClient.ft_metadata({ + tokenId: mechanismConfig.stakingContractAccountId, + }) : null; /** diff --git a/src/features/donation/models/effects.ts b/src/features/donation/models/effects.ts index dcffe4c1..7b746c31 100644 --- a/src/features/donation/models/effects.ts +++ b/src/features/donation/models/effects.ts @@ -16,7 +16,7 @@ import { donationContractClient, } from "@/common/contracts/core/donation"; import { PotDonation, PotDonationArgs, potContractClient } from "@/common/contracts/core/pot"; -import type { FungibleTokenMetadata } from "@/common/contracts/tokens/ft"; +import type { FungibleTokenMetadata } from "@/common/contracts/tokens/fungible"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId, TxExecutionStatus } from "@/common/types"; import { AppDispatcher } from "@/store"; diff --git a/src/features/profile-setup/models/deprecated.ts b/src/features/profile-setup/models/deprecated.ts index e86ce0ac..f3ec9580 100644 --- a/src/features/profile-setup/models/deprecated.ts +++ b/src/features/profile-setup/models/deprecated.ts @@ -5,7 +5,7 @@ import { createModel } from "@rematch/core"; import { prop } from "remeda"; -import { NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social"; +import { NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social-db"; import { getImage } from "@/common/services/images"; import { ByAccountId } from "@/common/types"; import { rootPathnames } from "@/pathnames"; diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index a3579867..55586372 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -10,7 +10,7 @@ import { MIN_PROPOSAL_DEPOSIT_FALLBACK, PUBLIC_GOODS_REGISTRY_LIST_ID, } from "@/common/constants"; -import { type NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social"; +import { type NEARSocialUserProfile, socialDbContractClient } from "@/common/contracts/social-db"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import { deepObjectDiff } from "@/common/lib"; import type { ByAccountId } from "@/common/types"; diff --git a/src/layout/profile/components/SmartContract.tsx b/src/layout/profile/components/SmartContract.tsx index ee5ab8ee..4f36735a 100644 --- a/src/layout/profile/components/SmartContract.tsx +++ b/src/layout/profile/components/SmartContract.tsx @@ -1,4 +1,4 @@ -import { NEARSocialUserProfile } from "@/common/contracts/social"; +import { NEARSocialUserProfile } from "@/common/contracts/social-db"; import { ClipboardCopyButton } from "@/common/ui/components"; const getProfileSmartContracts = (profile?: NEARSocialUserProfile) => { diff --git a/src/layout/profile/components/github-repos.tsx b/src/layout/profile/components/github-repos.tsx index 9ec10cd6..35747040 100644 --- a/src/layout/profile/components/github-repos.tsx +++ b/src/layout/profile/components/github-repos.tsx @@ -1,4 +1,4 @@ -import { NEARSocialUserProfile } from "@/common/contracts/social"; +import { NEARSocialUserProfile } from "@/common/contracts/social-db"; const convertURLtoGithubURL = (path: string) => { // If the path starts with "github.com/", return it as it is diff --git a/src/pages/profile/[accountId]/feed.tsx b/src/pages/profile/[accountId]/feed.tsx index b4f0c152..0fae89ec 100644 --- a/src/pages/profile/[accountId]/feed.tsx +++ b/src/pages/profile/[accountId]/feed.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import InfiniteScrollWrapper from "react-infinite-scroll-component"; import { fetchAccountFeedPosts } from "@/common/api/near-social-indexer"; -import { IndexPostResultItem } from "@/common/contracts/social"; +import { IndexPostResultItem } from "@/common/contracts/social-db"; import type { AccountId } from "@/common/types"; import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; diff --git a/src/pages/profile/[accountId]/funding-raised.tsx b/src/pages/profile/[accountId]/funding-raised.tsx index 1e691902..4b93f410 100644 --- a/src/pages/profile/[accountId]/funding-raised.tsx +++ b/src/pages/profile/[accountId]/funding-raised.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; import { styled } from "styled-components"; import { indexer } from "@/common/api/indexer"; -import { ExternalFundingSource } from "@/common/contracts/social"; +import { ExternalFundingSource } from "@/common/contracts/social-db"; import type { AccountId } from "@/common/types"; import { Separator } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; From a4d87f201e094605b9f04cbb0ccd2adedc3a74ae Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Wed, 29 Jan 2025 14:57:04 +0000 Subject: [PATCH 67/84] Fix hydration error --- src/pages/pots.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/pots.tsx b/src/pages/pots.tsx index 065e0e66..0e4dbec0 100644 --- a/src/pages/pots.tsx +++ b/src/pages/pots.tsx @@ -1,4 +1,3 @@ -import { isClient } from "@wpdas/naxios"; import Link from "next/link"; import { Button, PageWithBanner } from "@/common/ui/components"; @@ -20,7 +19,7 @@ const Banner = () => { "max-xs:w-full max-xs:p-[12px_0px] mt-6 mt-[40px] flex items-center gap-8 max-md:flex-col max-md:gap-4" } > - {isClient() && } + - )} -
- - {payoutDetails && ( -
- - Estimated Matched Amount - - - {estimatedMatchedAmount} -
- )} -
- )} - - ); -}; diff --git a/src/entities/project/constants.ts b/src/entities/project/constants.ts index 5ebe1c87..d8fe493c 100644 --- a/src/entities/project/constants.ts +++ b/src/entities/project/constants.ts @@ -1,15 +1,4 @@ -import { ProjectCategoryOption, ProjectListingStatusOption } from "./types"; - -export const categories: ProjectCategoryOption[] = [ - { label: "DeSci", val: "DeSci" }, - { label: "Open Source", val: "Open Source" }, - { label: "Non Profit", val: "Non Profit" }, - { label: "Social Impact", val: "Social Impact" }, - { label: "Climate", val: "Climate" }, - { label: "Public Good", val: "Public Good" }, - { label: "Community", val: "Community" }, - { label: "Education", val: "Education" }, -]; +import { ProjectListingStatusOption } from "./types"; export const statuses: ProjectListingStatusOption[] = [ { label: "All", val: "All" }, diff --git a/src/entities/project/hooks/lookup.ts b/src/entities/project/hooks/lookup.ts deleted file mode 100644 index 8c45b57d..00000000 --- a/src/entities/project/hooks/lookup.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo, useState } from "react"; - -import { indexer } from "@/common/api/indexer"; -import { oldToRecent } from "@/common/lib"; -import { ByListId, ChronologicalSortOrder, ChronologicalSortOrderVariant } from "@/common/types"; - -import { ProjectCategory, ProjectListingStatusVariant } from "../types"; - -export type ProjectLookupParams = ByListId & {}; - -export const useProjectLookup = ({ listId }: ProjectLookupParams) => { - const [pageNumber, setPageNumber] = useState(1); - const [searchTerm, setSearchTerm] = useState(undefined); - const [categoryFilter, setCategoryFilter] = useState([]); - - const [statusFilter, setStatusFilter] = useState("Approved"); - - const [sortingOrder, setSortingOrder] = useState( - ChronologicalSortOrder.recent, - ); - - const { - data: listRegistrations, - isLoading, - error, - } = indexer.useListRegistrations({ - listId, - category: categoryFilter.join(","), - status: statusFilter === "All" ? undefined : statusFilter, - page: pageNumber, - search: searchTerm, - }); - - const results = useMemo(() => { - const oldToRecentResults = oldToRecent("submitted_at", listRegistrations?.results ?? []); - - return sortingOrder === ChronologicalSortOrder.older - ? oldToRecentResults - : oldToRecentResults.toReversed(); - }, [listRegistrations?.results, sortingOrder]); - - return { - projectCategoryFilter: categoryFilter, - projectLookupError: error, - projectLookupPageNumber: pageNumber, - projectSearchTerm: searchTerm, - projectSortingOrder: sortingOrder, - projectStatusFilter: statusFilter, - setProjectCategoryFilter: setCategoryFilter, - setProjectLookupPageNumber: setPageNumber, - setProjectSearchTerm: setSearchTerm, - setProjectSortingOrder: setSortingOrder, - setProjectStatusFilter: setStatusFilter, - isProjectLookupPending: isLoading, - projects: results, - totalProjectCount: listRegistrations?.count ?? 0, - }; -}; diff --git a/src/entities/project/index.ts b/src/entities/project/index.ts index 2681dcd8..fffe6138 100644 --- a/src/entities/project/index.ts +++ b/src/entities/project/index.ts @@ -1,6 +1,3 @@ export * from "./constants"; + export * from "./types"; -export * from "./components/ProjectCard"; -export * from "./components/ProjectDiscovery"; -export * from "./components/Team"; -export * from "./hooks/lookup"; diff --git a/src/entities/project/types.ts b/src/entities/project/types.ts index 900f3712..ca842c9d 100644 --- a/src/entities/project/types.ts +++ b/src/entities/project/types.ts @@ -1,23 +1,5 @@ import { ListRegistrationStatus } from "@/common/api/indexer"; -export enum ProjectCategory { - "Social Impact" = "Social Impact", - "Non Profit" = "Non Profit", - "Climate" = "Climate", - "Public Good" = "Public Good", - "DeSci" = "DeSci", - "Open Source" = "Open Source", - "Community" = "Community", - "Education" = "Education", -} - -export type ProjectCategoryVariant = keyof typeof ProjectCategory; - -export type ProjectCategoryOption = { - label: string; - val: ProjectCategoryVariant; -}; - export type ProjectListingStatusVariant = | ListRegistrationStatus /** diff --git a/src/entities/project/utils/getTotalAmountNear.ts b/src/entities/project/utils/getTotalAmountNear.ts deleted file mode 100644 index e869516b..00000000 --- a/src/entities/project/utils/getTotalAmountNear.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Big } from "big.js"; - -import type { DirectDonation } from "@/common/contracts/core/donation"; -import type { PayoutDetailed, PotDonation } from "@/common/contracts/core/pot"; - -export const getTotalAmountNear = ( - donations: (PotDonation | DirectDonation)[], - potId?: string, - payoutDetails?: PayoutDetailed, -) => { - if (payoutDetails) return payoutDetails.totalAmount; // payout is set - if (!donations) return "0"; - let totalDonationAmountNear = Big(0); - - for (const donation of donations) { - if ( - ("ft_id" in donation && donation.ft_id === "near") || // For DirectDonation - potId - ) { - totalDonationAmountNear = totalDonationAmountNear.plus(Big(donation.total_amount)); - } - } - - return totalDonationAmountNear.toString(); -}; diff --git a/src/entities/project/utils/index.ts b/src/entities/project/utils/index.ts deleted file mode 100644 index c271d883..00000000 --- a/src/entities/project/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./getTotalAmountNear"; -export * from "./getTeamMembersFromProfile"; -export * from "./getTagsFromSocialProfileData"; diff --git a/src/features/donation/components/buttons.tsx b/src/features/donation/components/buttons.tsx index be955ad3..17bba43f 100644 --- a/src/features/donation/components/buttons.tsx +++ b/src/features/donation/components/buttons.tsx @@ -1,6 +1,6 @@ import { ByPotId, indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { ByCampaignId, ByListId } from "@/common/types"; +import { type ByAccountId, ByCampaignId, ByListId } from "@/common/types"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; @@ -38,6 +38,18 @@ export const DonateRandomly = () => { ); }; +export type DonateToAccountButtonProps = ByAccountId & {}; + +export const DonateToAccountButton: React.FC = ({ accountId }) => { + const { openDonationModal } = useDonation({ accountId }); + + return ( + + ); +}; + export type DonateToPotProjectsProps = ByPotId & {}; export const DonateToPotProjects: React.FC = ({ potId }) => { diff --git a/src/features/pot-configuration/components/PotConfigurationEditor.tsx b/src/features/pot-configuration/components/PotConfigurationEditor.tsx index 6342e704..1d146028 100644 --- a/src/features/pot-configuration/components/PotConfigurationEditor.tsx +++ b/src/features/pot-configuration/components/PotConfigurationEditor.tsx @@ -151,7 +151,7 @@ export const PotConfigurationEditor: React.FC = ({ type="number" min={0} max={100} - step={1} + step={0.01} classNames={{ root: "lg:w-50% w-full" }} {...field} /> diff --git a/src/features/profile-setup/components/form-elements.tsx b/src/features/profile-setup/components/form-elements.tsx index 31c661ca..196875d8 100644 --- a/src/features/profile-setup/components/form-elements.tsx +++ b/src/features/profile-setup/components/form-elements.tsx @@ -12,7 +12,7 @@ import { Textarea, } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { ProjectCategoryVariant } from "@/entities/project"; +import { ACCOUNT_CATEGORY_VARIANTS } from "@/entities/_shared/account"; import { SubTitle } from "./styles"; @@ -114,17 +114,6 @@ export const CustomInput = ({ ); -const options: ProjectCategoryVariant[] = [ - "Social Impact", - "Non Profit", - "Climate", - "Public Good", - "DeSci", - "Open Source", - "Community", - "Education", -]; - export const ProjectCategoryPicker = ({ onValuesChange, defaultValues, @@ -150,9 +139,9 @@ export const ProjectCategoryPicker = ({ - {options.map((option, i) => ( - - {option} + {ACCOUNT_CATEGORY_VARIANTS.map((categoryVariant) => ( + + {categoryVariant} ))} diff --git a/src/features/profile-setup/models/schemas.ts b/src/features/profile-setup/models/schemas.ts index d2988fb7..0be941cd 100644 --- a/src/features/profile-setup/models/schemas.ts +++ b/src/features/profile-setup/models/schemas.ts @@ -1,6 +1,6 @@ import { array, object, string } from "zod"; -import { ProjectCategory } from "@/entities/project"; +import { AccountCategory } from "@/entities/_shared/account"; export const addFundingSourceSchema = object({ investorName: string({ @@ -27,7 +27,7 @@ export const addFundingSourceSchema = object({ }); // TODO: Add cross-field validation to make repositories required -//! if one of the project categories is `ProjectCategory["Open Source"]` +//! if one of the project categories is `AccountCategory["Open Source"]` export const profileSetupSchema = object({ name: string() .min(3, "Must be at least 3 characters long") @@ -44,15 +44,14 @@ export const profileSetupSchema = object({ telegram: string().optional(), github: string().optional(), - teamMembers: array(string()).optional(), categories: array(string()).min(1), + githubRepositories: array(string()).optional(), + teamMembers: array(string()).optional(), + smartContracts: array(array(string())).optional(), + fundingSources: array(addFundingSourceSchema).optional(), publicGoodReason: string() .min(20, "Must contain at least 20 characters") .max(500, "Must be less 500 characters long") .optional(), - - smartContracts: array(array(string())).optional(), - fundingSources: array(addFundingSourceSchema).optional(), - githubRepositories: array(string()).optional(), }); diff --git a/src/entities/project/components/ProjectDiscovery.tsx b/src/layout/components/project-discovery.tsx similarity index 52% rename from src/entities/project/components/ProjectDiscovery.tsx rename to src/layout/components/project-discovery.tsx index 63dc3374..5bdd4bcf 100644 --- a/src/entities/project/components/ProjectDiscovery.tsx +++ b/src/layout/components/project-discovery.tsx @@ -1,8 +1,5 @@ -import { use, useMemo } from "react"; +import { useMemo } from "react"; -import Image from "next/image"; - -import { ListRegistration } from "@/common/api/indexer"; import { CHRONOLOGICAL_SORT_OPTIONS } from "@/common/constants"; import { type ByListId, ChronologicalSortOrderVariant } from "@/common/types"; import { @@ -20,43 +17,52 @@ import { SortSelect, } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; +import { + ACCOUNT_CATEGORY_OPTIONS, + AccountCard, + AccountCardSkeleton, +} from "@/entities/_shared/account"; +import { useListRegistrationLookup } from "@/entities/list"; +import { DonateToAccountButton } from "@/features/donation"; -import { ProjectCard } from "./ProjectCard"; -import { categories, statuses } from "../constants"; -import { ProjectCardSkeleton } from "./ProjectCardSkeleton"; -import { useProjectLookup } from "../hooks/lookup"; +import { statuses } from "../../entities/project/constants"; -const ProjectLookupPlaceholder = () => - Array.from({ length: 6 }, (_, index) => ); +const ListRegistrationLookupPlaceholder = () => + Array.from({ length: 6 }, (_, index) => ); -export type ProjectDiscoveryProps = ByListId & {}; +export type ProjectDiscoveryProps = ByListId & { + noResultsPlaceholder?: React.ReactNode; +}; -export const ProjectDiscovery: React.FC = ({ listId }) => { +export const ProjectDiscovery: React.FC = ({ + listId, + noResultsPlaceholder, +}) => { const { - projectCategoryFilter, - setProjectCategoryFilter, - projectLookupPageNumber, - setProjectLookupPageNumber, - projectSearchTerm, - setProjectSearchTerm, - setProjectSortingOrder, - projectStatusFilter, - setProjectStatusFilter, - isProjectLookupPending, - projects, - totalProjectCount, - } = useProjectLookup({ listId }); + isPending, + categoryFilter, + currentPageNumber, + results: listRegistrations, + searchTerm, + statusFilter, + totalCount, + setCategoryFilter, + setCurrentPageNumber, + setSearchTerm, + setSortingOrder, + setStatusFilter, + } = useListRegistrationLookup({ listId }); const tagList = useMemo( () => [ { label: "Category", - options: categories, + options: ACCOUNT_CATEGORY_OPTIONS, type: GroupType.multiple, props: { - value: projectCategoryFilter, - onValueChange: setProjectCategoryFilter, + value: categoryFilter, + onValueChange: setCategoryFilter, }, } as Group, @@ -66,17 +72,17 @@ export const ProjectDiscovery: React.FC = ({ listId }) => type: GroupType.single, props: { - value: projectStatusFilter, - onValueChange: setProjectStatusFilter, + value: statusFilter, + onValueChange: setStatusFilter, }, } as Group, ], - [projectCategoryFilter, projectStatusFilter, setProjectCategoryFilter, setProjectStatusFilter], + [categoryFilter, statusFilter, setCategoryFilter, setStatusFilter], ); const pageNumberButtons = useMemo(() => { - const totalPages = Math.ceil(totalProjectCount / 30); + const totalPages = Math.ceil(totalCount / 30); const pages: (number | "ellipsis")[] = []; if (totalPages <= 7) { @@ -86,10 +92,10 @@ export const ProjectDiscovery: React.FC = ({ listId }) => // Always show first page pages.push(1); - if (projectLookupPageNumber <= 4) { + if (currentPageNumber <= 4) { // Near start pages.push(2, 3, 4, 5, "ellipsis", totalPages); - } else if (projectLookupPageNumber >= totalPages - 3) { + } else if (currentPageNumber >= totalPages - 3) { // Near end pages.push( "ellipsis", @@ -103,9 +109,9 @@ export const ProjectDiscovery: React.FC = ({ listId }) => // Middle pages.push( "ellipsis", - projectLookupPageNumber - 1, - projectLookupPageNumber, - projectLookupPageNumber + 1, + currentPageNumber - 1, + currentPageNumber, + currentPageNumber + 1, "ellipsis", totalPages, ); @@ -118,9 +124,9 @@ export const ProjectDiscovery: React.FC = ({ listId }) => ) : ( setProjectLookupPageNumber(page)} + onClick={() => setCurrentPageNumber(page)} className={cn({ - "border-black font-bold": projectLookupPageNumber === page, + "border-black font-bold": currentPageNumber === page, })} > {page} @@ -128,23 +134,23 @@ export const ProjectDiscovery: React.FC = ({ listId }) => )} )); - }, [projectLookupPageNumber, setProjectLookupPageNumber, totalProjectCount]); + }, [currentPageNumber, setCurrentPageNumber, totalCount]); - const numberOfPages = useMemo(() => Math.ceil(totalProjectCount / 30), [totalProjectCount]); + const numberOfPages = useMemo(() => Math.ceil(totalCount / 30), [totalCount]); return (
{"All projects"} - {totalProjectCount} + {totalCount}
setProjectSearchTerm(e.target.value.toLowerCase())} + defaultValue={searchTerm} + onChange={(e) => setSearchTerm(e.target.value.toLowerCase())} /> @@ -152,27 +158,33 @@ export const ProjectDiscovery: React.FC = ({ listId }) => { - setProjectSortingOrder(value as ChronologicalSortOrderVariant); + setSortingOrder(value as ChronologicalSortOrderVariant); }} />
- {isProjectLookupPending ? ( - + {listRegistrations.length === 0 ? ( + <>{isPending ? : (noResultsPlaceholder ?? null)} ) : ( - projects.map((registration: ListRegistration) => ( - + listRegistrations.map(({ id, registrant: registrantAccount }) => ( + } + /> )) )}
+ {numberOfPages > 1 && ( setProjectLookupPageNumber((prev) => Math.max(prev - 1, 1))} + onClick={() => setCurrentPageNumber((prev) => Math.max(prev - 1, 1))} /> @@ -181,31 +193,13 @@ export const ProjectDiscovery: React.FC = ({ listId }) => - setProjectLookupPageNumber((prev) => - Math.min(prev + 1, Math.ceil(totalProjectCount / 30)), - ) + setCurrentPageNumber((prev) => Math.min(prev + 1, Math.ceil(totalCount / 30))) } /> )} - - {!totalProjectCount && ( -
- No results found - -
-

{"No results found"}

-
-
- )}
); }; diff --git a/src/entities/project/components/Team.tsx b/src/layout/profile/components/team.tsx similarity index 95% rename from src/entities/project/components/Team.tsx rename to src/layout/profile/components/team.tsx index b4d0b4ac..b13dc47e 100644 --- a/src/entities/project/components/Team.tsx +++ b/src/layout/profile/components/team.tsx @@ -63,11 +63,11 @@ const Members = ({ team }: { team?: string[] }) => { return members; }; -type Props = { +export type ProfileLayoutTeamProps = { profile?: NEARSocialUserProfile; }; -export const Team = ({ profile }: Props) => { +export const ProfileLayoutTeam: React.FC = ({ profile }) => { const [team, setTeam] = useState(getProfileTeamMembersData(profile)); useEffect(() => { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 455ea8a3..10e44133 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,3 +1,4 @@ +import Image from "next/image"; import Link from "next/link"; import { NETWORK } from "@/common/_config"; @@ -6,8 +7,9 @@ import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { useViewerSession } from "@/common/viewer"; -import { ProjectCard, ProjectDiscovery } from "@/entities/project"; -import { DonateRandomly } from "@/features/donation"; +import { AccountCard } from "@/entities/_shared/account"; +import { DonateRandomly, DonateToAccountButton } from "@/features/donation"; +import { ProjectDiscovery } from "@/layout/components/project-discovery"; import { rootPathnames } from "@/pathnames"; export const FEATURED_PROJECT_ACCOUNT_IDS = @@ -103,13 +105,34 @@ export default function Home() {
- {FEATURED_PROJECT_ACCOUNT_IDS.map((projectId) => ( - + {FEATURED_PROJECT_ACCOUNT_IDS.map((projectAccountId) => ( + } + /> ))}
- + + No results found + +
+

{"No results found"}

+
+
+ } + />
); } diff --git a/src/pages/pot/[potId]/projects.tsx b/src/pages/pot/[potId]/projects.tsx index b2a9f367..9ea2daee 100644 --- a/src/pages/pot/[potId]/projects.tsx +++ b/src/pages/pot/[potId]/projects.tsx @@ -4,7 +4,8 @@ import { useRouter } from "next/router"; import { PotApplication, PotApplicationStatus, indexer } from "@/common/api/indexer"; import { InfiniteScroll, SearchBar } from "@/common/ui/components"; -import { ProjectCard } from "@/entities/project"; +import { AccountCard } from "@/entities/_shared/account"; +import { DonateToAccountButton } from "@/features/donation"; import { PotLayout } from "@/layout/pot/components/layout"; const handleSearch = ( @@ -67,8 +68,12 @@ export default function PotProjectsTab() { index={index} setIndex={setIndex} size={9} - renderItem={({ applicant }: PotApplication) => ( - + renderItem={({ applicant: applicantAccount }: PotApplication) => ( + } + /> )} /> ) : ( diff --git a/src/pages/profile/[accountId]/home.tsx b/src/pages/profile/[accountId]/home.tsx index 3c5276b2..57e3c37b 100644 --- a/src/pages/profile/[accountId]/home.tsx +++ b/src/pages/profile/[accountId]/home.tsx @@ -9,10 +9,10 @@ import { listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; import type { AccountId } from "@/common/types"; import { useAccountSocialProfile } from "@/entities/_shared/account"; -import { Team } from "@/entities/project"; import { ProfileLayoutGithubRepos } from "@/layout/profile/components/github-repos"; import { ProfileLayout } from "@/layout/profile/components/layout"; import SmartContracts from "@/layout/profile/components/SmartContract"; +import { ProfileLayoutTeam } from "@/layout/profile/components/team"; const Section: React.FC<{ title: string; text?: string; children?: React.ReactNode }> = ({ title, @@ -64,7 +64,7 @@ export default function ProfileHomeTab() {
)} - +
From 2b0a98205a5268caea6b6aafe56e1003574faa07 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Fri, 31 Jan 2025 18:59:32 +0000 Subject: [PATCH 73/84] wip --- src/common/_deprecated/pots.ts | 40 ----------- src/common/ui/form-fields/index.ts | 1 - src/common/viewer/components.tsx | 12 ---- .../adapters/user.ts} | 15 ++-- src/common/wallet/components.tsx | 71 +++++++++++++++++++ .../{viewer => wallet}/hooks/session.ts | 8 +-- src/common/{viewer => wallet}/index.ts | 0 .../internal/wallet-provider.tsx | 4 +- src/common/{viewer => wallet}/types.ts | 2 +- .../components/AccountFollowButton.tsx | 4 +- .../account}/components/github-repos.tsx | 2 +- .../account/components/smart-contracts.tsx} | 4 +- src/entities/_shared/account/index.ts | 2 + src/entities/_shared/index.ts | 2 + .../_shared/token/components/TokenIcon.tsx | 2 +- .../token/components/TokenSelector.tsx | 6 +- .../token/components/TokenTotalValue.tsx | 2 +- .../_shared/token/components/inputs.tsx} | 5 +- .../_shared/token/hooks/_deprecated.ts} | 0 .../_shared/token/{hooks.ts => hooks/data.ts} | 2 +- src/entities/_shared/token/index.ts | 3 +- .../campaign/components/CampaignBanner.tsx | 2 +- .../campaign/components/CampaignForm.tsx | 3 +- .../campaign/components/CampaignSettings.tsx | 4 +- src/entities/campaign/hooks/forms.ts | 4 +- src/entities/list/components/ListCard.tsx | 4 +- src/entities/list/components/ListDetails.tsx | 4 +- .../list/components/ListFormDetails.tsx | 4 +- src/entities/list/hooks/useAllLists.ts | 4 +- src/entities/post/components/PostEditor.tsx | 4 +- .../pot/components/ChallengeModal.tsx | 4 +- src/entities/pot/hooks/useOrderedDonations.ts | 34 ++++++++- .../voting-round/components/CandidateRow.tsx | 4 +- .../components/CandidateTable.tsx | 4 +- .../components/VoteWeightBreakdown.tsx | 4 +- src/entities/voting-round/hooks/candidates.ts | 6 +- src/entities/voting-round/hooks/clearance.ts | 4 +- .../donation/components/DonationFlow.tsx | 4 +- .../donation/components/DonationModal.tsx | 4 +- .../components/DonationSybilWarning.tsx | 4 +- .../components/DonationTokenBalance.tsx | 4 +- .../pot-application/hooks/clearance.ts | 4 +- .../components/PotConfigurationPreview.tsx | 4 +- src/features/pot-configuration/hooks/forms.ts | 4 +- .../pot-deployment/components/buttons.tsx | 4 +- .../components/payout-manager.tsx | 4 +- src/layout/components/app-bar.tsx | 4 +- src/layout/components/user-dropdown.tsx | 4 +- src/layout/pot/components/layout-hero.tsx | 4 +- .../_deprecated/useDonationsForProject.ts | 2 +- .../profile/_deprecated/useDonationsSent.ts | 2 +- src/layout/profile/components/summary.tsx | 6 +- src/pages/_app.tsx | 6 +- src/pages/index.tsx | 4 +- src/pages/pot/[potId]/feed.tsx | 4 +- src/pages/pot/[potId]/votes.tsx | 4 +- src/pages/profile/[accountId]/edit.tsx | 4 +- src/pages/profile/[accountId]/feed.tsx | 4 +- src/pages/profile/[accountId]/home.tsx | 12 ++-- src/pages/register.tsx | 4 +- 60 files changed, 219 insertions(+), 161 deletions(-) delete mode 100644 src/common/_deprecated/pots.ts delete mode 100644 src/common/viewer/components.tsx rename src/common/{viewer/internal/wallet-context.ts => wallet/adapters/user.ts} (58%) create mode 100644 src/common/wallet/components.tsx rename src/common/{viewer => wallet}/hooks/session.ts (92%) rename src/common/{viewer => wallet}/index.ts (100%) rename src/common/{viewer => wallet}/internal/wallet-provider.tsx (95%) rename src/common/{viewer => wallet}/types.ts (97%) rename src/{layout/profile => entities/_shared/account}/components/github-repos.tsx (94%) rename src/{layout/profile/components/SmartContract.tsx => entities/_shared/account/components/smart-contracts.tsx} (92%) create mode 100644 src/entities/_shared/index.ts rename src/{common/ui/form-fields/nearinput.tsx => entities/_shared/token/components/inputs.tsx} (93%) rename src/{common/_deprecated/useNearToUsdWithFallback.ts => entities/_shared/token/hooks/_deprecated.ts} (100%) rename src/entities/_shared/token/{hooks.ts => hooks/data.ts} (98%) diff --git a/src/common/_deprecated/pots.ts b/src/common/_deprecated/pots.ts deleted file mode 100644 index 81e416d5..00000000 --- a/src/common/_deprecated/pots.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { INDEXER_API_ENDPOINT_URL } from "@/common/_config"; -import { Donation, PotPayout } from "@/common/api/indexer/types"; - -type GetPotPayoutsResponse = { - count: number; - next?: string; - previous?: string; - results: PotPayout[]; -}; - -export const getPotPayouts = async ({ potId, pageSize }: { potId: string; pageSize?: number }) => { - const res = await fetch( - `${INDEXER_API_ENDPOINT_URL}/api/v1/pots/${potId}/payouts${pageSize ? `?page_size=${pageSize}` : ""}`, - ); - - const json = await res.json(); - return json as GetPotPayoutsResponse; -}; - -type GetPotDonationsResponse = { - count: number; - next?: string; - previous?: string; - results: Donation[]; -}; - -export const getPotDonations = async ({ - potId, - pageSize, -}: { - potId: string; - pageSize?: number; -}) => { - const res = await fetch( - `${INDEXER_API_ENDPOINT_URL}/api/v1/pots/${potId}/donations${pageSize ? `?page_size=${9999}` : ""}`, - ); - - const json = await res.json(); - return json as GetPotDonationsResponse; -}; diff --git a/src/common/ui/form-fields/index.ts b/src/common/ui/form-fields/index.ts index f00028b9..257171f9 100644 --- a/src/common/ui/form-fields/index.ts +++ b/src/common/ui/form-fields/index.ts @@ -2,4 +2,3 @@ export * from "./checkbox"; export * from "./select"; export * from "./text"; export * from "./textarea"; -export * from "./nearinput"; diff --git a/src/common/viewer/components.tsx b/src/common/viewer/components.tsx deleted file mode 100644 index b9370dda..00000000 --- a/src/common/viewer/components.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { IS_CLIENT } from "../constants"; -import { WalletProvider } from "./internal/wallet-provider"; - -export type ViewerSessionProviderProps = { - children: React.ReactNode; -}; - -/** - * Required for wallet and session bindings to be available on the client. - */ -export const ViewerSessionProvider: React.FC = ({ children }) => - IS_CLIENT ? {children} : children; diff --git a/src/common/viewer/internal/wallet-context.ts b/src/common/wallet/adapters/user.ts similarity index 58% rename from src/common/viewer/internal/wallet-context.ts rename to src/common/wallet/adapters/user.ts index 6a15e652..2f8b16f4 100644 --- a/src/common/viewer/internal/wallet-context.ts +++ b/src/common/wallet/adapters/user.ts @@ -7,27 +7,30 @@ type WalletAccountState = { isSignedIn: boolean; }; -type WalletContextState = { error: null | unknown } & ( +type WalletUserAdapterContextState = { error: null | unknown } & ( | { isReady: false; isSignedIn: false; accountId: undefined } | ({ isReady: true } & WalletAccountState) ); -const initialWalletContextState: WalletContextState = { +const initialWalletUserAdapterContextState: WalletUserAdapterContextState = { isReady: false, isSignedIn: false, accountId: undefined, error: null, }; -type WalletContextStore = WalletContextState & { +type WalletUserAdapterContextStore = WalletUserAdapterContextState & { registerInit: (isReady: boolean) => void; setAccountState: (state: WalletAccountState) => void; setError: (error: unknown) => void; }; -export const useWalletContextStore = create((set) => ({ - ...initialWalletContextState, - registerInit: (isReady: boolean) => set(isReady ? { isReady } : initialWalletContextState), +export const useWalletUserAdapterContext = create((set) => ({ + ...initialWalletUserAdapterContextState, + + registerInit: (isReady: boolean) => + set(isReady ? { isReady } : initialWalletUserAdapterContextState), + setAccountState: (newAccountState: WalletAccountState) => set(newAccountState), setError: (error: unknown) => set({ error }), })); diff --git a/src/common/wallet/components.tsx b/src/common/wallet/components.tsx new file mode 100644 index 00000000..82869b2d --- /dev/null +++ b/src/common/wallet/components.tsx @@ -0,0 +1,71 @@ +import { useCallback, useEffect } from "react"; + +import { nearProtocolClient } from "@/common/api/near-protocol"; +import { IS_CLIENT } from "@/common/constants"; + +import { useWalletUserAdapterContext } from "./adapters/user"; + +type WalletProviderProps = { + children: React.ReactNode; +}; + +const WalletProvider: React.FC = ({ children }) => { + const { registerInit, setAccountState, setError, isReady, isSignedIn, accountId, error } = + useWalletUserAdapterContext(); + + const syncWalletState = useCallback(() => { + const isWalletSignedIn = nearProtocolClient.walletApi.walletSelector.isSignedIn(); + const walletAccountId = nearProtocolClient.walletApi.accountId; + + if (isWalletSignedIn !== isSignedIn || walletAccountId !== accountId) { + setAccountState({ accountId: walletAccountId, isSignedIn: isWalletSignedIn }); + } + }, [accountId, isSignedIn, setAccountState]); + + useEffect(() => { + if (!isReady && error === null) { + nearProtocolClient.walletApi + .initNear() + .then(() => registerInit(true)) + .catch((error) => { + console.log(error); + setError(error); + }); + } + }, [error, isReady, registerInit, setError]); + + useEffect(() => { + if (isReady) { + syncWalletState(); + + nearProtocolClient.walletApi.walletSelector.on("signedIn", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("signedOut", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("accountsChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("networkChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.on("uriChanged", syncWalletState); + } + + return () => { + if (isReady) { + nearProtocolClient.walletApi.walletSelector.off("signedIn", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("signedOut", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("networkChanged", syncWalletState); + nearProtocolClient.walletApi.walletSelector.off("uriChanged", syncWalletState); + } + }; + }, [syncWalletState, isReady]); + + return children; +}; + +export type WalletUserSessionProviderProps = { + children: React.ReactNode; +}; + +/** + * Required for wallet and session bindings to be available on the client. + */ +export const WalletUserSessionProvider: React.FC = ({ + children, +}) => (IS_CLIENT ? {children} : children); diff --git a/src/common/viewer/hooks/session.ts b/src/common/wallet/hooks/session.ts similarity index 92% rename from src/common/viewer/hooks/session.ts rename to src/common/wallet/hooks/session.ts index ac624aa1..f64c4ce2 100644 --- a/src/common/viewer/hooks/session.ts +++ b/src/common/wallet/hooks/session.ts @@ -8,11 +8,11 @@ import { sybilResistanceContractHooks } from "@/common/contracts/core/sybil-resi import { isAccountId } from "@/common/lib"; import { useGlobalStoreSelector } from "@/store"; -import { useWalletContextStore } from "../internal/wallet-context"; -import { ViewerSession } from "../types"; +import { useWalletUserAdapterContext } from "../adapters/user"; +import { WalletUserSession } from "../types"; -export const useViewerSession = (): ViewerSession => { - const wallet = useWalletContextStore(); +export const useWalletUserSession = (): WalletUserSession => { + const wallet = useWalletUserAdapterContext(); const { actAsDao } = useGlobalStoreSelector(prop("nav")); const daoAccountId = actAsDao.defaultAddress; const isDaoAccountIdValid = useMemo(() => isAccountId(daoAccountId), [daoAccountId]); diff --git a/src/common/viewer/index.ts b/src/common/wallet/index.ts similarity index 100% rename from src/common/viewer/index.ts rename to src/common/wallet/index.ts diff --git a/src/common/viewer/internal/wallet-provider.tsx b/src/common/wallet/internal/wallet-provider.tsx similarity index 95% rename from src/common/viewer/internal/wallet-provider.tsx rename to src/common/wallet/internal/wallet-provider.tsx index ceba844e..fcb8e5a1 100644 --- a/src/common/viewer/internal/wallet-provider.tsx +++ b/src/common/wallet/internal/wallet-provider.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect } from "react"; import { nearProtocolClient } from "@/common/api/near-protocol"; -import { useWalletContextStore } from "./wallet-context"; +import { useWalletUserAdapterContext } from "../adapters/user"; export type WalletProviderProps = { children: React.ReactNode; @@ -10,7 +10,7 @@ export type WalletProviderProps = { export const WalletProvider: React.FC = ({ children }) => { const { registerInit, setAccountState, setError, isReady, isSignedIn, accountId, error } = - useWalletContextStore(); + useWalletUserAdapterContext(); const syncWalletState = useCallback(() => { const isWalletSignedIn = nearProtocolClient.walletApi.walletSelector.isSignedIn(); diff --git a/src/common/viewer/types.ts b/src/common/wallet/types.ts similarity index 97% rename from src/common/viewer/types.ts rename to src/common/wallet/types.ts index 524cad80..a8d60dd5 100644 --- a/src/common/viewer/types.ts +++ b/src/common/wallet/types.ts @@ -5,7 +5,7 @@ type ViewerDaoRepresentativeParams = | { isDaoRepresentative: false; daoAccountId: undefined } | { isDaoRepresentative: true; daoAccountId: AccountId }; -export type ViewerSession = +export type WalletUserSession = | { hasWalletReady: false; accountId: undefined; diff --git a/src/entities/_shared/account/components/AccountFollowButton.tsx b/src/entities/_shared/account/components/AccountFollowButton.tsx index 8553228c..d739e392 100644 --- a/src/entities/_shared/account/components/AccountFollowButton.tsx +++ b/src/entities/_shared/account/components/AccountFollowButton.tsx @@ -5,7 +5,7 @@ import { socialDbContractClient } from "@/common/contracts/social-db"; import type { ByAccountId } from "@/common/types"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; export type AccountFollowButtonProps = ByAccountId & { className?: string; @@ -15,7 +15,7 @@ export const AccountFollowButton: React.FC = ({ accountId, className, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { isLoading: isFollowerListLoading, diff --git a/src/layout/profile/components/github-repos.tsx b/src/entities/_shared/account/components/github-repos.tsx similarity index 94% rename from src/layout/profile/components/github-repos.tsx rename to src/entities/_shared/account/components/github-repos.tsx index 35747040..ebe2f583 100644 --- a/src/layout/profile/components/github-repos.tsx +++ b/src/entities/_shared/account/components/github-repos.tsx @@ -18,7 +18,7 @@ const formatProfileGithubRepositories = (profile?: NEARSocialUserProfile) => { return githubRepos as string[]; }; -export const ProfileLayoutGithubRepos = ({ profile }: { profile?: NEARSocialUserProfile }) => { +export const AccountGithubReposList = ({ profile }: { profile?: NEARSocialUserProfile }) => { const githubRepositories = formatProfileGithubRepositories(profile); if (githubRepositories.length > 0) { diff --git a/src/layout/profile/components/SmartContract.tsx b/src/entities/_shared/account/components/smart-contracts.tsx similarity index 92% rename from src/layout/profile/components/SmartContract.tsx rename to src/entities/_shared/account/components/smart-contracts.tsx index 4f36735a..ffdf9694 100644 --- a/src/layout/profile/components/SmartContract.tsx +++ b/src/entities/_shared/account/components/smart-contracts.tsx @@ -19,7 +19,7 @@ const getProfileSmartContracts = (profile?: NEARSocialUserProfile) => { return smartContracts as [string, string][]; }; -const SmartContract = ({ profile }: { profile?: NEARSocialUserProfile }) => { +export const AccountSmartContractsList = ({ profile }: { profile?: NEARSocialUserProfile }) => { const smartContracts = getProfileSmartContracts(profile); return ( @@ -38,5 +38,3 @@ const SmartContract = ({ profile }: { profile?: NEARSocialUserProfile }) => {

); }; - -export default SmartContract; diff --git a/src/entities/_shared/account/index.ts b/src/entities/_shared/account/index.ts index 8b0a2ad4..8887dce9 100644 --- a/src/entities/_shared/account/index.ts +++ b/src/entities/_shared/account/index.ts @@ -12,7 +12,9 @@ export * from "./components/AccountProfileTags"; export * from "./components/AccountSummaryPopup"; export * from "./components/card"; export * from "./components/card-skeleton"; +export * from "./components/github-repos"; export * from "./components/profile-images"; +export * from "./components/smart-contracts"; export * from "./hooks/power"; export * from "./hooks/social-profile"; diff --git a/src/entities/_shared/index.ts b/src/entities/_shared/index.ts new file mode 100644 index 00000000..df9079ee --- /dev/null +++ b/src/entities/_shared/index.ts @@ -0,0 +1,2 @@ +export * from "./account"; +export * from "./token"; diff --git a/src/entities/_shared/token/components/TokenIcon.tsx b/src/entities/_shared/token/components/TokenIcon.tsx index 73505270..8c4008da 100644 --- a/src/entities/_shared/token/components/TokenIcon.tsx +++ b/src/entities/_shared/token/components/TokenIcon.tsx @@ -5,7 +5,7 @@ import { AccountId } from "@/common/types"; import { NearIcon } from "@/common/ui/svg"; import { cn } from "@/common/ui/utils"; -import { useToken } from "../hooks"; +import { useToken } from "../hooks/data"; type TokenIconSize = "xs" | "sm" | "md"; diff --git a/src/entities/_shared/token/components/TokenSelector.tsx b/src/entities/_shared/token/components/TokenSelector.tsx index 238351c7..2575b79e 100644 --- a/src/entities/_shared/token/components/TokenSelector.tsx +++ b/src/entities/_shared/token/components/TokenSelector.tsx @@ -1,12 +1,12 @@ import { NATIVE_TOKEN_ID } from "@/common/constants"; import type { ByTokenId } from "@/common/types"; import { SelectField, SelectFieldOption, SelectFieldProps } from "@/common/ui/form-fields"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; -import { useToken, useTokenAllowlist } from "../hooks"; +import { useToken, useTokenAllowlist } from "../hooks/data"; const TokenSelectorOption: React.FC = ({ tokenId }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data: token } = useToken({ tokenId, diff --git a/src/entities/_shared/token/components/TokenTotalValue.tsx b/src/entities/_shared/token/components/TokenTotalValue.tsx index 36c85a24..383daecc 100644 --- a/src/entities/_shared/token/components/TokenTotalValue.tsx +++ b/src/entities/_shared/token/components/TokenTotalValue.tsx @@ -4,8 +4,8 @@ import { ByTokenId } from "@/common/types"; import { Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useToken } from "../hooks"; import { TokenIcon } from "./TokenIcon"; +import { useToken } from "../hooks/data"; export type TokenTotalValueProps = ByTokenId & ({ amountFloat: number } | { amountBigString: string }) & { diff --git a/src/common/ui/form-fields/nearinput.tsx b/src/entities/_shared/token/components/inputs.tsx similarity index 93% rename from src/common/ui/form-fields/nearinput.tsx rename to src/entities/_shared/token/components/inputs.tsx index 50c6f9a2..5921ff14 100644 --- a/src/common/ui/form-fields/nearinput.tsx +++ b/src/entities/_shared/token/components/inputs.tsx @@ -1,11 +1,10 @@ import { forwardRef } from "react"; import { NATIVE_TOKEN_ID } from "@/common/constants"; +import { FormControl, FormItem, FormLabel } from "@/common/ui/components"; +import { TextField } from "@/common/ui/form-fields"; import { useTokenUsdDisplayValue } from "@/entities/_shared/token"; -import { TextField } from "./text"; -import { FormControl, FormItem, FormLabel } from "../components"; - export interface InputFieldProps extends React.InputHTMLAttributes {} export type NearInputFieldProps = InputFieldProps & { diff --git a/src/common/_deprecated/useNearToUsdWithFallback.ts b/src/entities/_shared/token/hooks/_deprecated.ts similarity index 100% rename from src/common/_deprecated/useNearToUsdWithFallback.ts rename to src/entities/_shared/token/hooks/_deprecated.ts diff --git a/src/entities/_shared/token/hooks.ts b/src/entities/_shared/token/hooks/data.ts similarity index 98% rename from src/entities/_shared/token/hooks.ts rename to src/entities/_shared/token/hooks/data.ts index b98b5d46..c5d066b4 100644 --- a/src/entities/_shared/token/hooks.ts +++ b/src/entities/_shared/token/hooks/data.ts @@ -12,7 +12,7 @@ import { indivisibleUnitsToBigNum, indivisibleUnitsToFloat, isAccountId } from " import { formatWithCommas } from "@/common/lib/formatWithCommas"; import type { AccountId, ByTokenId, ConditionalActivation } from "@/common/types"; -import { type TokenQuery, type TokenQueryResult } from "./types"; +import { type TokenQuery, type TokenQueryResult } from "../types"; /** * @deprecated Use `usdPrice` Big number from `useToken({ tokenId: ... })` diff --git a/src/entities/_shared/token/index.ts b/src/entities/_shared/token/index.ts index b53d3c4d..0015ffd4 100644 --- a/src/entities/_shared/token/index.ts +++ b/src/entities/_shared/token/index.ts @@ -1,7 +1,8 @@ export * from "./types"; +export * from "./components/inputs"; export * from "./components/TokenIcon"; export * from "./components/TokenSelector"; export * from "./components/TokenTotalValue"; -export * from "./hooks"; +export * from "./hooks/data"; diff --git a/src/entities/campaign/components/CampaignBanner.tsx b/src/entities/campaign/components/CampaignBanner.tsx index fa82de5c..9fdc9e3e 100644 --- a/src/entities/campaign/components/CampaignBanner.tsx +++ b/src/entities/campaign/components/CampaignBanner.tsx @@ -1,6 +1,5 @@ import { LazyLoadImage } from "react-lazy-load-image-component"; -import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; import { campaignsContractHooks } from "@/common/contracts/core/campaigns"; import { yoctoNearToFloat } from "@/common/lib"; import getTimePassed from "@/common/lib/getTimePassed"; @@ -8,6 +7,7 @@ import type { ByCampaignId } from "@/common/types"; import { SocialsShare } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { AccountProfileLink } from "@/entities/_shared/account"; +import { useNearToUsdWithFallback } from "@/entities/_shared/token/hooks/_deprecated"; import { DonateToCampaignProjects } from "@/features/donation"; import { CampaignProgressBar } from "./CampaignProgressBar"; diff --git a/src/entities/campaign/components/CampaignForm.tsx b/src/entities/campaign/components/CampaignForm.tsx index d37aacc8..ed667254 100644 --- a/src/entities/campaign/components/CampaignForm.tsx +++ b/src/entities/campaign/components/CampaignForm.tsx @@ -5,7 +5,8 @@ import { Campaign } from "@/common/contracts/core/campaigns"; import { useRouteQuery, yoctoNearToFloat } from "@/common/lib"; import { nearSocialIpfsUpload } from "@/common/services/ipfs"; import { Button, Form, FormField } from "@/common/ui/components"; -import { NearInputField, TextAreaField, TextField } from "@/common/ui/form-fields"; +import { TextAreaField, TextField } from "@/common/ui/form-fields"; +import { NearInputField } from "@/entities/_shared"; import { useCampaignForm } from "../hooks/forms"; diff --git a/src/entities/campaign/components/CampaignSettings.tsx b/src/entities/campaign/components/CampaignSettings.tsx index 299f5792..f9c24e0c 100644 --- a/src/entities/campaign/components/CampaignSettings.tsx +++ b/src/entities/campaign/components/CampaignSettings.tsx @@ -7,7 +7,7 @@ import { yoctoNearToFloat } from "@/common/lib"; import type { ByCampaignId } from "@/common/types"; import { Skeleton } from "@/common/ui/components"; import { NearIcon } from "@/common/ui/svg"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountProfilePicture } from "@/entities/_shared/account"; import { CampaignForm } from "./CampaignForm"; @@ -45,7 +45,7 @@ const CampaignSettingsBarCardSkeleton = () => export type CampaignSettingsProps = ByCampaignId & {}; export const CampaignSettings: React.FC = ({ campaignId }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const [openEditCampaign, setOpenEditCampaign] = useState(false); const { diff --git a/src/entities/campaign/hooks/forms.ts b/src/entities/campaign/hooks/forms.ts index b220b1c5..3abb4989 100644 --- a/src/entities/campaign/hooks/forms.ts +++ b/src/entities/campaign/hooks/forms.ts @@ -7,14 +7,14 @@ import { infer as FromSchema } from "zod"; import { campaignsContractClient } from "@/common/contracts/core/campaigns"; import { floatToYoctoNear, useRouteQuery } from "@/common/lib"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { dispatch } from "@/store"; import { campaignFormSchema } from "../models/schema"; import { CampaignEnumType } from "../types"; export const useCampaignForm = () => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { // TODO: Pass this values down from the page level! diff --git a/src/entities/list/components/ListCard.tsx b/src/entities/list/components/ListCard.tsx index 3c0df5e7..22c9592b 100644 --- a/src/entities/list/components/ListCard.tsx +++ b/src/entities/list/components/ListCard.tsx @@ -9,7 +9,7 @@ import { listsContractClient } from "@/common/contracts/core/lists"; import { truncate } from "@/common/lib"; import { LayersIcon } from "@/common/ui/svg"; import { LikeIcon } from "@/common/ui/svg/like"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountProfilePicture } from "@/entities/_shared/account"; import { dispatch } from "@/store"; @@ -24,7 +24,7 @@ export const ListCard = ({ background?: string; backdrop: string; }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const [isUpvoted, setIsUpvoted] = useState(false); const { push } = useRouter(); diff --git a/src/entities/list/components/ListDetails.tsx b/src/entities/list/components/ListDetails.tsx index a224feda..ae534423 100644 --- a/src/entities/list/components/ListDetails.tsx +++ b/src/entities/list/components/ListDetails.tsx @@ -20,7 +20,7 @@ import { } from "@/common/ui/components"; import { SocialsShare } from "@/common/ui/components/molecules/social-share"; import { AdminUserIcon, DeleteListIcon, DotsIcons, PenIcon } from "@/common/ui/svg"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountGroupEditModal, AccountListItem, @@ -43,7 +43,7 @@ interface ListDetailsType { } export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { push, diff --git a/src/entities/list/components/ListFormDetails.tsx b/src/entities/list/components/ListFormDetails.tsx index f8e9aef0..e9b820f4 100644 --- a/src/entities/list/components/ListFormDetails.tsx +++ b/src/entities/list/components/ListFormDetails.tsx @@ -17,7 +17,7 @@ import { nearSocialIpfsUpload } from "@/common/services/ipfs"; import type { ByListId } from "@/common/types"; import { Button, Input } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountGroup, AccountListItem, AccountProfilePicture } from "@/entities/_shared/account"; import { dispatch } from "@/store"; @@ -50,7 +50,7 @@ export type ListFormProps = Partial & { }; export const ListFormDetails: React.FC = ({ listId, isDuplicate = false }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { back, push } = useRouter(); const onEditPage = listId === undefined; diff --git a/src/entities/list/hooks/useAllLists.ts b/src/entities/list/hooks/useAllLists.ts index 9cc63e1e..2f05d6c2 100644 --- a/src/entities/list/hooks/useAllLists.ts +++ b/src/entities/list/hooks/useAllLists.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { List, indexer } from "@/common/api/indexer"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { ListOverviewType } from "../types"; @@ -13,7 +13,7 @@ export const useAllLists = ( setFilteredRegistrations: SetRegistrationsFn, currentListType?: ListOverviewType, ) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const [registrations, setRegistrations] = useState([]); const [administratedListsOnly, setAdministratedListsOnly] = useState(false); diff --git a/src/entities/post/components/PostEditor.tsx b/src/entities/post/components/PostEditor.tsx index 1c24df57..6a3aac91 100644 --- a/src/entities/post/components/PostEditor.tsx +++ b/src/entities/post/components/PostEditor.tsx @@ -5,11 +5,11 @@ import { LazyLoadImage } from "react-lazy-load-image-component"; import { socialDbContractClient } from "@/common/contracts/social-db"; import { AccountId } from "@/common/types"; import { Button, Textarea } from "@/common/ui/components"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useAccountSocialProfile } from "@/entities/_shared/account"; export const PostEditor = ({ accountId }: { accountId: AccountId }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { avatarSrc } = useAccountSocialProfile({ enabled: viewer.isSignedIn, diff --git a/src/entities/pot/components/ChallengeModal.tsx b/src/entities/pot/components/ChallengeModal.tsx index 2b042e52..ee39876b 100644 --- a/src/entities/pot/components/ChallengeModal.tsx +++ b/src/entities/pot/components/ChallengeModal.tsx @@ -14,7 +14,7 @@ import { Spinner, Textarea, } from "@/common/ui/components"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useChallengeForm } from "../hooks/forms"; @@ -30,7 +30,7 @@ export const ChallengeModal: React.FC = ({ potId, potDetail, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data: potPayoutChallenges } = potContractHooks.usePayoutChallenges({ potId }); const activeChallenge = useMemo(() => { diff --git a/src/entities/pot/hooks/useOrderedDonations.ts b/src/entities/pot/hooks/useOrderedDonations.ts index 0f09a8bc..1707dbb3 100644 --- a/src/entities/pot/hooks/useOrderedDonations.ts +++ b/src/entities/pot/hooks/useOrderedDonations.ts @@ -1,10 +1,42 @@ import { useEffect, useState } from "react"; -import { getPotDonations, getPotPayouts } from "@/common/_deprecated/pots"; +import { INDEXER_API_ENDPOINT_URL } from "@/common/_config"; import { Donation, PotPayout } from "@/common/api/indexer"; import { SUPPORTED_FTS } from "@/common/constants"; import { formatWithCommas, yoctoNearToFloat } from "@/common/lib"; +type GetPotPayoutsResponse = { + count: number; + next?: string; + previous?: string; + results: PotPayout[]; +}; + +const getPotPayouts = async ({ potId, pageSize }: { potId: string; pageSize?: number }) => { + const res = await fetch( + `${INDEXER_API_ENDPOINT_URL}/api/v1/pots/${potId}/payouts${pageSize ? `?page_size=${pageSize}` : ""}`, + ); + + const json = await res.json(); + return json as GetPotPayoutsResponse; +}; + +type GetPotDonationsResponse = { + count: number; + next?: string; + previous?: string; + results: Donation[]; +}; + +const getPotDonations = async ({ potId, pageSize }: { potId: string; pageSize?: number }) => { + const res = await fetch( + `${INDEXER_API_ENDPOINT_URL}/api/v1/pots/${potId}/donations${pageSize ? `?page_size=${9999}` : ""}`, + ); + + const json = await res.json(); + return json as GetPotDonationsResponse; +}; + export type JoinDonation = { id: string; nearAmount: number; diff --git a/src/entities/voting-round/components/CandidateRow.tsx b/src/entities/voting-round/components/CandidateRow.tsx index 1ff8aaa1..d49d0faf 100644 --- a/src/entities/voting-round/components/CandidateRow.tsx +++ b/src/entities/voting-round/components/CandidateRow.tsx @@ -6,7 +6,7 @@ import { Dot } from "lucide-react"; import { ByElectionId, Candidate } from "@/common/contracts/core/voting"; import { Button, Checkbox, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountListItem } from "@/entities/_shared/account"; import { useVotingRoundCandidateEntry } from "../hooks/candidates"; @@ -25,7 +25,7 @@ export const VotingRoundCandidateRow: React.FC = ( isSelected = false, onSelect, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { isLoading, canReceiveVotes, hasUserVotes, handleVoteCast } = useVotingRoundCandidateEntry( { electionId, accountId }, diff --git a/src/entities/voting-round/components/CandidateTable.tsx b/src/entities/voting-round/components/CandidateTable.tsx index 70bc263a..5f09c165 100644 --- a/src/entities/voting-round/components/CandidateTable.tsx +++ b/src/entities/voting-round/components/CandidateTable.tsx @@ -13,7 +13,7 @@ import { AccountId } from "@/common/types"; import { Button, ScrollArea } from "@/common/ui/components"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { VotingRoundCandidateRow } from "./CandidateRow"; @@ -30,7 +30,7 @@ export const VotingRoundCandidateTable: React.FC }) => { const { height: windowHeight } = useWindowSize(); const { toast } = useToast(); - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const selectedEntries = useSet(); const { data: isVotingPeriodOngoing } = votingContractHooks.useIsVotingPeriod({ diff --git a/src/entities/voting-round/components/VoteWeightBreakdown.tsx b/src/entities/voting-round/components/VoteWeightBreakdown.tsx index 4ab3712b..c3647d74 100644 --- a/src/entities/voting-round/components/VoteWeightBreakdown.tsx +++ b/src/entities/voting-round/components/VoteWeightBreakdown.tsx @@ -12,7 +12,7 @@ import { Separator, } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useVotingRoundVoterVoteWeight, @@ -34,7 +34,7 @@ export const VotingRoundVoteWeightBreakdown: React.FC { const isDialogOpen = useMemo(() => open && mode === "modal", [mode, open]); - const { accountId } = useViewerSession(); + const { accountId } = useWalletUserSession(); const { voteWeight } = useVotingRoundVoterVoteWeight({ accountId, potId }); const voteWeightAmplifiers = useVotingRoundVoterVoteWeightAmplifiers({ accountId, potId }); diff --git a/src/entities/voting-round/hooks/candidates.ts b/src/entities/voting-round/hooks/candidates.ts index 997805da..0563ea4a 100644 --- a/src/entities/voting-round/hooks/candidates.ts +++ b/src/entities/voting-round/hooks/candidates.ts @@ -9,13 +9,13 @@ import { import { isAccountId } from "@/common/lib"; import { type AccountId, ByAccountId } from "@/common/types"; import { useToast } from "@/common/ui/hooks"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; export interface VotingRoundCandidateLookup extends ByElectionId {} export const useVotingRoundCandidateLookup = ({ electionId }: VotingRoundCandidateLookup) => { const [searchTerm, setSearchTerm] = useState(""); - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data, ...candidatesQueryResult } = votingContractHooks.useElectionCandidates({ enabled: electionId !== 0, @@ -67,7 +67,7 @@ export const useVotingRoundCandidateEntry = ({ accountId, }: ByElectionId & ByAccountId) => { const { toast } = useToast(); - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data: isVotingPeriodOngoing = false } = votingContractHooks.useIsVotingPeriod({ enabled: electionId !== 0, diff --git a/src/entities/voting-round/hooks/clearance.ts b/src/entities/voting-round/hooks/clearance.ts index ea52db55..b7f14c4b 100644 --- a/src/entities/voting-round/hooks/clearance.ts +++ b/src/entities/voting-round/hooks/clearance.ts @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { prop } from "remeda"; import { ClearanceCheckResult } from "@/common/types"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; // TODO: refactor to support multi-mechanism for the V2 milestone /** @@ -11,7 +11,7 @@ import { useViewerSession } from "@/common/viewer"; * as it's built for the mpDAO milestone. */ export const useVotingRoundSessionClearance = (): ClearanceCheckResult => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); return useMemo(() => { const requirements = [ diff --git a/src/features/donation/components/DonationFlow.tsx b/src/features/donation/components/DonationFlow.tsx index 2d84942a..23c1b8cd 100644 --- a/src/features/donation/components/DonationFlow.tsx +++ b/src/features/donation/components/DonationFlow.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { isBigSource, useRouteQuery } from "@/common/lib"; import { Button, DialogFooter, Form, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useToken } from "@/entities/_shared/token"; import { dispatch } from "@/store"; @@ -25,7 +25,7 @@ export const DonationFlow: React.FC = ({ closeModal, ...props }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { currentStep, finalOutcome } = useDonationState(); const { diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index 8d6c5987..0be10c0a 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -6,7 +6,7 @@ import { nearProtocolClient } from "@/common/api/near-protocol"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { dispatch } from "@/store"; import { DonationFlow, DonationFlowProps } from "./DonationFlow"; @@ -17,7 +17,7 @@ export type DonationModalProps = DonationAllocationKey & Pick & {}; export const DonationModal = create((props: DonationModalProps) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const self = useModal(); const isSingleProjectDonation = "accountId" in props; const isPotDonation = "potId" in props; diff --git a/src/features/donation/components/DonationSybilWarning.tsx b/src/features/donation/components/DonationSybilWarning.tsx index b3a4778c..5a649c94 100644 --- a/src/features/donation/components/DonationSybilWarning.tsx +++ b/src/features/donation/components/DonationSybilWarning.tsx @@ -4,7 +4,7 @@ import { SYBIL_APP_LINK_URL } from "@/common/_config"; import { ByPotId, indexer } from "@/common/api/indexer"; import { Alert, AlertDescription, AlertTitle, Button } from "@/common/ui/components"; import { WarningIcon } from "@/common/ui/svg"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; export type DonationSybilWarningProps = ByPotId & { classNames?: { @@ -16,7 +16,7 @@ export const DonationSybilWarning: React.FC = ({ potId, classNames, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data: pot } = indexer.usePot({ potId }); const isVisible = useMemo( diff --git a/src/features/donation/components/DonationTokenBalance.tsx b/src/features/donation/components/DonationTokenBalance.tsx index f9de6346..fad17a6e 100644 --- a/src/features/donation/components/DonationTokenBalance.tsx +++ b/src/features/donation/components/DonationTokenBalance.tsx @@ -1,7 +1,7 @@ import { ByTokenId } from "@/common/types"; import { Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useToken } from "@/entities/_shared/token"; export type DonationTokenBalanceProps = ByTokenId & { @@ -12,7 +12,7 @@ export const DonationTokenBalance: React.FC = ({ tokenId, classNames, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data: token, error: tokenError } = useToken({ balanceCheckAccountId: viewer?.accountId, diff --git a/src/features/pot-application/hooks/clearance.ts b/src/features/pot-application/hooks/clearance.ts index 99dbbcb4..7634906c 100644 --- a/src/features/pot-application/hooks/clearance.ts +++ b/src/features/pot-application/hooks/clearance.ts @@ -7,7 +7,7 @@ import { ByPotId, indexer } from "@/common/api/indexer"; import { METAPOOL_MPDAO_VOTING_POWER_DECIMALS } from "@/common/contracts/metapool"; import { indivisibleUnitsToBigNum } from "@/common/lib"; import { type AccountId, ClearanceCheckResult } from "@/common/types"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useToken } from "@/entities/_shared/token"; import { POT_APPLICATION_REQUIREMENTS_MPDAO } from "../constants"; @@ -24,7 +24,7 @@ export const usePotApplicationUserClearance = ({ const { staking } = POT_APPLICATION_REQUIREMENTS_MPDAO; const { data: pot } = indexer.usePot({ potId }); - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { data: voterInfo } = indexer.useMpdaoVoter({ enabled: viewer.isSignedIn, diff --git a/src/features/pot-configuration/components/PotConfigurationPreview.tsx b/src/features/pot-configuration/components/PotConfigurationPreview.tsx index f8908262..38c7541e 100644 --- a/src/features/pot-configuration/components/PotConfigurationPreview.tsx +++ b/src/features/pot-configuration/components/PotConfigurationPreview.tsx @@ -7,7 +7,7 @@ import { ByPotId, type PotId, indexer } from "@/common/api/indexer"; import { isAccountId } from "@/common/lib"; import { Button, DataLoadingPlaceholder, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountGroup, AccountListItem, AccountProfileLink } from "@/entities/_shared/account"; import { POT_EDITOR_FIELDS } from "../constants"; @@ -59,7 +59,7 @@ export const PotConfigurationPreview: React.FC = ( onEditClick, className, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { isLoading, data: pot } = indexer.usePot({ enabled: potId !== undefined, diff --git a/src/features/pot-configuration/hooks/forms.ts b/src/features/pot-configuration/hooks/forms.ts index 6f1b610f..5c1d3b4c 100644 --- a/src/features/pot-configuration/hooks/forms.ts +++ b/src/features/pot-configuration/hooks/forms.ts @@ -9,7 +9,7 @@ import { CONTRACT_SOURCECODE_REPO_URL, CONTRACT_SOURCECODE_VERSION } from "@/com import { ByPotId, type PotId, indexer } from "@/common/api/indexer"; import { PotConfig } from "@/common/contracts/core/pot"; import { AccountId } from "@/common/types"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { PotInputs } from "@/entities/pot"; import { donationFeeBasisPointsToPercents } from "@/features/donation"; import { rootPathnames } from "@/pathnames"; @@ -33,7 +33,7 @@ export const usePotConfigurationEditorForm = ({ schema, ...props }: PotConfigurationEditorFormArgs) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const router = useRouter(); const potId = "potId" in props ? props.potId : undefined; const isNewPot = "potId" in props && typeof potId !== "string"; diff --git a/src/features/pot-deployment/components/buttons.tsx b/src/features/pot-deployment/components/buttons.tsx index dacb6fd3..7519f66c 100644 --- a/src/features/pot-deployment/components/buttons.tsx +++ b/src/features/pot-deployment/components/buttons.tsx @@ -2,12 +2,12 @@ import Link from "next/link"; import { PLATFORM_NAME } from "@/common/_config"; import { Button, Skeleton, Tooltip, TooltipContent, TooltipTrigger } from "@/common/ui/components"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useAccountPower } from "@/entities/_shared/account"; import { rootPathnames } from "@/pathnames"; export const PotDeploymentButton: React.FC = () => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { isLoading: isViewerPowerLoading, data: viewerPower } = useAccountPower({ accountId: viewer.accountId, diff --git a/src/features/proportional-funding/components/payout-manager.tsx b/src/features/proportional-funding/components/payout-manager.tsx index 069cfb56..9d05d136 100644 --- a/src/features/proportional-funding/components/payout-manager.tsx +++ b/src/features/proportional-funding/components/payout-manager.tsx @@ -6,7 +6,7 @@ import type { ByPotId } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; import { Button, Skeleton } from "@/common/ui/components"; import { useToast } from "@/common/ui/hooks"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { useToken } from "@/entities/_shared/token"; import { usePotAuthorization } from "@/entities/pot"; import { VotingRoundResultsTable, useVotingRoundResults } from "@/entities/voting-round"; @@ -22,7 +22,7 @@ export const ProportionalFundingPayoutManager: React.FC { const { toast } = useToast(); - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); const votingRoundResults = useVotingRoundResults({ potId }); diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index b8363621..6ec5bb51 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -8,7 +8,7 @@ import { NETWORK } from "@/common/_config"; import { nearProtocolClient } from "@/common/api/near-protocol"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { CartLink } from "@/entities/cart"; import { rootPathnames } from "@/pathnames"; @@ -34,7 +34,7 @@ const links = [ ]; const AuthButton = () => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const onSignInClick = useCallback(() => { nearProtocolClient.walletApi.signInModal(); diff --git a/src/layout/components/user-dropdown.tsx b/src/layout/components/user-dropdown.tsx index 78928198..df90554e 100644 --- a/src/layout/components/user-dropdown.tsx +++ b/src/layout/components/user-dropdown.tsx @@ -15,7 +15,7 @@ import { DropdownMenuTrigger, Skeleton, } from "@/common/ui/components"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountProfilePicture, useAccountSocialProfile } from "@/entities/_shared/account"; import { listRegistrationStatuses } from "@/entities/list"; import { rootPathnames } from "@/pathnames"; @@ -23,7 +23,7 @@ import { rootPathnames } from "@/pathnames"; import { DaoAuth } from "./dao-auth"; export const UserDropdown = () => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { profile } = useAccountSocialProfile({ enabled: viewer.isSignedIn, diff --git a/src/layout/pot/components/layout-hero.tsx b/src/layout/pot/components/layout-hero.tsx index 66439449..a92012ff 100644 --- a/src/layout/pot/components/layout-hero.tsx +++ b/src/layout/pot/components/layout-hero.tsx @@ -11,7 +11,7 @@ import { potContractHooks } from "@/common/contracts/core/pot"; import { Button, Checklist, ClipboardCopyButton, Skeleton } from "@/common/ui/components"; import { VolunteerIcon } from "@/common/ui/svg"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { TokenTotalValue } from "@/entities/_shared/token"; import { PotDonationStats, @@ -37,7 +37,7 @@ export const PotLayoutHero: React.FC = ({ onChallengePayoutsClick, onFundMatchingPoolClick, }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); const { data: pot } = indexer.usePot({ potId }); const { data: potPayoutChallenges } = potContractHooks.usePayoutChallenges({ potId }); diff --git a/src/layout/profile/_deprecated/useDonationsForProject.ts b/src/layout/profile/_deprecated/useDonationsForProject.ts index 2774f644..66f47cfa 100644 --- a/src/layout/profile/_deprecated/useDonationsForProject.ts +++ b/src/layout/profile/_deprecated/useDonationsForProject.ts @@ -2,8 +2,8 @@ import { useEffect, useMemo, useState } from "react"; import { Big } from "big.js"; -import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; import { SUPPORTED_FTS } from "@/common/constants"; +import { useNearToUsdWithFallback } from "@/entities/_shared/token/hooks/_deprecated"; import { DonationInfo, getAccountDonationsReceived } from "@/layout/profile/_deprecated/accounts"; /** diff --git a/src/layout/profile/_deprecated/useDonationsSent.ts b/src/layout/profile/_deprecated/useDonationsSent.ts index 895f588c..06f5f7a3 100644 --- a/src/layout/profile/_deprecated/useDonationsSent.ts +++ b/src/layout/profile/_deprecated/useDonationsSent.ts @@ -2,9 +2,9 @@ import { useEffect, useMemo, useState } from "react"; import { Big } from "big.js"; -import { useNearToUsdWithFallback } from "@/common/_deprecated/useNearToUsdWithFallback"; import { Donation, indexer } from "@/common/api/indexer"; import { SUPPORTED_FTS } from "@/common/constants"; +import { useNearToUsdWithFallback } from "@/entities/_shared/token/hooks/_deprecated"; import { DonationInfo } from "./accounts"; diff --git a/src/layout/profile/components/summary.tsx b/src/layout/profile/components/summary.tsx index aa1dfa7a..0f3b9d61 100644 --- a/src/layout/profile/components/summary.tsx +++ b/src/layout/profile/components/summary.tsx @@ -13,7 +13,7 @@ import { Button, ClipboardCopyButton } from "@/common/ui/components"; import CheckIcon from "@/common/ui/svg/CheckIcon"; import ReferrerIcon from "@/common/ui/svg/ReferrerIcon"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountFollowButton, AccountProfileLinktree, @@ -24,7 +24,7 @@ import { useDonation } from "@/features/donation"; import { rootPathnames } from "@/pathnames"; const Linktree: React.FC = ({ accountId }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const [copied, setCopied] = useState(false); return ( @@ -118,7 +118,7 @@ const Container = styled.div` export type ProfileLayoutSummaryProps = ByAccountId & {}; export const ProfileLayoutSummary: React.FC = ({ accountId }) => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const isOwner = viewer?.accountId === accountId; const { openDonationModal } = useDonation({ accountId }); const { isLoading: isProfileDataLoading, profile } = useAccountSocialProfile({ accountId }); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 898cb287..5e177451 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -20,7 +20,7 @@ import { APP_METADATA } from "@/common/constants"; import { TooltipProvider } from "@/common/ui/components"; import { Toaster } from "@/common/ui/components/molecules/toaster"; import { cn } from "@/common/ui/utils"; -import { ViewerSessionProvider } from "@/common/viewer"; +import { WalletUserSessionProvider } from "@/common/wallet"; import { AppBar } from "@/layout/components/app-bar"; import { dispatch, store } from "@/store"; @@ -44,7 +44,7 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) const getLayout = Component.getLayout ?? ((page) => page); return ( - + {APP_METADATA.title} @@ -66,6 +66,6 @@ export default function RootLayout({ Component, pageProps }: AppPropsWithLayout) - + ); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 10e44133..229a98c7 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,7 +6,7 @@ import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { Button } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { AccountCard } from "@/entities/_shared/account"; import { DonateRandomly, DonateToAccountButton } from "@/features/donation"; import { ProjectDiscovery } from "@/layout/components/project-discovery"; @@ -47,7 +47,7 @@ export const GeneralStats = () => { }; const WelcomeBanner = () => { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); return (
([]); const [isLoading, setIsLoading] = useState(true); const [isPotApplicantsReady, setIsPotApplicantsReady] = useState(false); diff --git a/src/pages/pot/[potId]/votes.tsx b/src/pages/pot/[potId]/votes.tsx index 7b7ec57f..aada06e7 100644 --- a/src/pages/pot/[potId]/votes.tsx +++ b/src/pages/pot/[potId]/votes.tsx @@ -25,7 +25,7 @@ import { } from "@/common/ui/components"; import { useMediaQuery } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { VotingRoundCandidateFilter, VotingRoundCandidateTable, @@ -39,7 +39,7 @@ import { import { PotLayout } from "@/layout/pot/components/layout"; export default function PotVotesTab() { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { query: routeQuery } = useRouter(); const { potId } = routeQuery as { potId: string }; const isDesktop = useMediaQuery("(min-width: 1024px)"); diff --git a/src/pages/profile/[accountId]/edit.tsx b/src/pages/profile/[accountId]/edit.tsx index 7b44ddc5..b70491ad 100644 --- a/src/pages/profile/[accountId]/edit.tsx +++ b/src/pages/profile/[accountId]/edit.tsx @@ -16,14 +16,14 @@ import { import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { ProfileSetupForm } from "@/features/profile-setup"; import { rootPathnames } from "@/pathnames"; export default function EditProjectPage() { const router = useRouter(); // const { accountId } = router.query as { accountId: AccountId }; - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const { toast } = useToast(); const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = diff --git a/src/pages/profile/[accountId]/feed.tsx b/src/pages/profile/[accountId]/feed.tsx index 0fae89ec..106cbcde 100644 --- a/src/pages/profile/[accountId]/feed.tsx +++ b/src/pages/profile/[accountId]/feed.tsx @@ -7,7 +7,7 @@ import { fetchAccountFeedPosts } from "@/common/api/near-social-indexer"; import { IndexPostResultItem } from "@/common/contracts/social-db"; import type { AccountId } from "@/common/types"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { PostCard, PostEditor } from "@/entities/post"; import { ProfileLayout } from "@/layout/profile/components/layout"; @@ -38,7 +38,7 @@ const NoResults = () => ( export default function ProfileFeedTab() { const router = useRouter(); const { accountId } = router.query as { accountId: AccountId }; - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const [posts, setPosts] = useState([]); const [offset, setOffset] = useState(40); const [isLoading, setIsLoading] = useState(false); diff --git a/src/pages/profile/[accountId]/home.tsx b/src/pages/profile/[accountId]/home.tsx index 57e3c37b..a93cba47 100644 --- a/src/pages/profile/[accountId]/home.tsx +++ b/src/pages/profile/[accountId]/home.tsx @@ -8,10 +8,12 @@ import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { listsContractHooks } from "@/common/contracts/core/lists"; import { isAccountId } from "@/common/lib"; import type { AccountId } from "@/common/types"; -import { useAccountSocialProfile } from "@/entities/_shared/account"; -import { ProfileLayoutGithubRepos } from "@/layout/profile/components/github-repos"; +import { + AccountGithubReposList, + AccountSmartContractsList, + useAccountSocialProfile, +} from "@/entities/_shared"; import { ProfileLayout } from "@/layout/profile/components/layout"; -import SmartContracts from "@/layout/profile/components/SmartContract"; import { ProfileLayoutTeam } from "@/layout/profile/components/team"; const Section: React.FC<{ title: string; text?: string; children?: React.ReactNode }> = ({ @@ -67,11 +69,11 @@ export default function ProfileHomeTab() {
- +
- +
)} diff --git a/src/pages/register.tsx b/src/pages/register.tsx index 89a52954..d8c85769 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -9,12 +9,12 @@ import { Alert, AlertDescription, AlertTitle, PageWithBanner } from "@/common/ui import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; -import { useViewerSession } from "@/common/viewer"; +import { useWalletUserSession } from "@/common/wallet"; import { ProfileSetupForm } from "@/features/profile-setup"; import { rootPathnames } from "@/pathnames"; export default function RegisterPage() { - const viewer = useViewerSession(); + const viewer = useWalletUserSession(); const router = useRouter(); const { toast } = useToast(); From 58ac1e66faaf4c072c5782c0184786d339925bad Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Fri, 31 Jan 2025 19:16:22 +0000 Subject: [PATCH 74/84] Reorganize common hierarchy --- src/common/api/near-protocol/index.ts | 4 -- .../near-protocol/client.ts | 0 .../near-protocol/hooks.ts | 0 src/common/blockchains/near-protocol/index.ts | 4 ++ .../near-protocol/web3modal.ts | 0 src/common/contracts/core/campaigns/client.ts | 2 +- src/common/contracts/core/donation/client.ts | 2 +- src/common/contracts/core/lists/client.ts | 2 +- .../contracts/core/pot-factory/client.ts | 2 +- src/common/contracts/core/pot/client.ts | 2 +- .../contracts/core/sybil-resistance/client.ts | 2 +- src/common/contracts/core/voting/client.ts | 2 +- .../metapool/liquid-staking/client.ts | 2 +- .../ref-finance/ref-exchange/client.ts | 2 +- src/common/contracts/social-db/client.ts | 2 +- src/common/contracts/sputnik-dao/index.ts | 2 +- .../contracts/tokens/fungible/client.ts | 2 +- src/common/lib/protocol-config.ts | 2 +- src/common/services/images.ts | 2 +- src/common/wallet/adapters.ts | 33 +++++++++++ src/common/wallet/adapters/user.ts | 36 ----------- src/common/wallet/components.tsx | 6 +- .../wallet/{hooks/session.ts => hooks.ts} | 6 +- src/common/wallet/index.ts | 2 +- .../wallet/internal/wallet-provider.tsx | 59 ------------------- src/entities/_shared/account/model/schemas.ts | 2 +- src/entities/_shared/token/hooks/data.ts | 6 +- src/entities/_shared/token/types.ts | 2 +- src/entities/campaign/models/effects.ts | 2 +- src/entities/campaign/models/schema.ts | 2 +- src/entities/dao/utils/validation.ts | 2 +- src/entities/list/components/AccountCard.tsx | 2 +- src/entities/list/components/ListHero.tsx | 2 +- src/entities/list/hooks/useListForm.ts | 2 +- src/entities/list/models/effects.ts | 2 +- src/entities/pot/hooks/forms.ts | 2 +- .../donation/components/DonationModal.tsx | 2 +- src/features/donation/models/effects.ts | 2 +- .../matching-pool-contribution/hooks/forms.ts | 2 +- src/features/pot-application/hooks/forms.ts | 2 +- .../pot-configuration/model/effects.ts | 2 +- src/features/profile-setup/models/effects.ts | 2 +- src/layout/components/app-bar.tsx | 2 +- src/layout/components/user-dropdown.tsx | 2 +- src/pages/pot/[potId]/votes.tsx | 2 +- 45 files changed, 80 insertions(+), 142 deletions(-) delete mode 100644 src/common/api/near-protocol/index.ts rename src/common/{api => blockchains}/near-protocol/client.ts (100%) rename src/common/{api => blockchains}/near-protocol/hooks.ts (100%) create mode 100644 src/common/blockchains/near-protocol/index.ts rename src/common/{api => blockchains}/near-protocol/web3modal.ts (100%) create mode 100644 src/common/wallet/adapters.ts delete mode 100644 src/common/wallet/adapters/user.ts rename src/common/wallet/{hooks/session.ts => hooks.ts} (94%) delete mode 100644 src/common/wallet/internal/wallet-provider.tsx diff --git a/src/common/api/near-protocol/index.ts b/src/common/api/near-protocol/index.ts deleted file mode 100644 index ad2db8c1..00000000 --- a/src/common/api/near-protocol/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as nearProtocolClient from "./client"; -import * as nearHooks from "./hooks"; - -export { nearProtocolClient, nearHooks }; diff --git a/src/common/api/near-protocol/client.ts b/src/common/blockchains/near-protocol/client.ts similarity index 100% rename from src/common/api/near-protocol/client.ts rename to src/common/blockchains/near-protocol/client.ts diff --git a/src/common/api/near-protocol/hooks.ts b/src/common/blockchains/near-protocol/hooks.ts similarity index 100% rename from src/common/api/near-protocol/hooks.ts rename to src/common/blockchains/near-protocol/hooks.ts diff --git a/src/common/blockchains/near-protocol/index.ts b/src/common/blockchains/near-protocol/index.ts new file mode 100644 index 00000000..95e4b815 --- /dev/null +++ b/src/common/blockchains/near-protocol/index.ts @@ -0,0 +1,4 @@ +import * as nearProtocolClient from "./client"; +import * as nearProtocolHooks from "./hooks"; + +export { nearProtocolClient, nearProtocolHooks }; diff --git a/src/common/api/near-protocol/web3modal.ts b/src/common/blockchains/near-protocol/web3modal.ts similarity index 100% rename from src/common/api/near-protocol/web3modal.ts rename to src/common/blockchains/near-protocol/web3modal.ts diff --git a/src/common/contracts/core/campaigns/client.ts b/src/common/contracts/core/campaigns/client.ts index 2f6d9e10..00e83162 100644 --- a/src/common/contracts/core/campaigns/client.ts +++ b/src/common/contracts/core/campaigns/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { CAMPAIGNS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; diff --git a/src/common/contracts/core/donation/client.ts b/src/common/contracts/core/donation/client.ts index 6e853666..acd33181 100644 --- a/src/common/contracts/core/donation/client.ts +++ b/src/common/contracts/core/donation/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { DONATION_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FULL_TGAS } from "@/common/constants"; import { diff --git a/src/common/contracts/core/lists/client.ts b/src/common/contracts/core/lists/client.ts index fe58ffbc..3c1c16f6 100644 --- a/src/common/contracts/core/lists/client.ts +++ b/src/common/contracts/core/lists/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { LISTS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; diff --git a/src/common/contracts/core/pot-factory/client.ts b/src/common/contracts/core/pot-factory/client.ts index fe70be9b..169672c9 100644 --- a/src/common/contracts/core/pot-factory/client.ts +++ b/src/common/contracts/core/pot-factory/client.ts @@ -2,7 +2,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { Big } from "big.js"; import { POT_FACTORY_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FULL_TGAS } from "@/common/constants"; import { PotArgs, PotDeploymentResult, PotFactoryConfig } from "./interfaces"; diff --git a/src/common/contracts/core/pot/client.ts b/src/common/contracts/core/pot/client.ts index b0d9e420..79972caf 100644 --- a/src/common/contracts/core/pot/client.ts +++ b/src/common/contracts/core/pot/client.ts @@ -2,7 +2,7 @@ import { MemoryCache, calculateDepositByDataSize } from "@wpdas/naxios"; import { parseNearAmount } from "near-api-js/lib/utils/format"; import { PotId } from "@/common/api/indexer"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { FULL_TGAS, ONE_HUNDREDTH_NEAR } from "@/common/constants"; import { diff --git a/src/common/contracts/core/sybil-resistance/client.ts b/src/common/contracts/core/sybil-resistance/client.ts index 986756d6..f358c0a9 100644 --- a/src/common/contracts/core/sybil-resistance/client.ts +++ b/src/common/contracts/core/sybil-resistance/client.ts @@ -1,7 +1,7 @@ import { Provider } from "near-api-js/lib/providers"; import { SYBIL_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { FULL_TGAS, ONE_HUNDREDTH_NEAR, TWO_HUNDREDTHS_NEAR } from "@/common/constants"; import { AccountId } from "@/common/types"; diff --git a/src/common/contracts/core/voting/client.ts b/src/common/contracts/core/voting/client.ts index a33dd999..0b24b2b7 100644 --- a/src/common/contracts/core/voting/client.ts +++ b/src/common/contracts/core/voting/client.ts @@ -1,6 +1,6 @@ import naxios, { MemoryCache } from "@wpdas/naxios"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import type { AccountId, diff --git a/src/common/contracts/metapool/liquid-staking/client.ts b/src/common/contracts/metapool/liquid-staking/client.ts index 39076b4d..92475137 100644 --- a/src/common/contracts/metapool/liquid-staking/client.ts +++ b/src/common/contracts/metapool/liquid-staking/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { METAPOOL_LIQUID_STAKING_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { IndivisibleUnits } from "@/common/types"; export const contractApi = naxiosInstance.contractApi({ diff --git a/src/common/contracts/ref-finance/ref-exchange/client.ts b/src/common/contracts/ref-finance/ref-exchange/client.ts index bbb22da7..fdb0c1b7 100644 --- a/src/common/contracts/ref-finance/ref-exchange/client.ts +++ b/src/common/contracts/ref-finance/ref-exchange/client.ts @@ -1,7 +1,7 @@ import { MemoryCache } from "@wpdas/naxios"; import { REF_EXCHANGE_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import type { AccountId } from "@/common/types"; const contractApi = naxiosInstance.contractApi({ diff --git a/src/common/contracts/social-db/client.ts b/src/common/contracts/social-db/client.ts index 5781dd02..b58427b7 100644 --- a/src/common/contracts/social-db/client.ts +++ b/src/common/contracts/social-db/client.ts @@ -1,7 +1,7 @@ import { buildTransaction } from "@wpdas/naxios"; import { SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { AccountId } from "@/common/types"; /** diff --git a/src/common/contracts/sputnik-dao/index.ts b/src/common/contracts/sputnik-dao/index.ts index 43f1af8d..505b8003 100644 --- a/src/common/contracts/sputnik-dao/index.ts +++ b/src/common/contracts/sputnik-dao/index.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; export const getDaoPolicy = async (accountId: string) => { try { diff --git a/src/common/contracts/tokens/fungible/client.ts b/src/common/contracts/tokens/fungible/client.ts index 7e2de8ce..c665382c 100644 --- a/src/common/contracts/tokens/fungible/client.ts +++ b/src/common/contracts/tokens/fungible/client.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import type { AccountId, ByAccountId, ByTokenId } from "@/common/types"; import type { FungibleTokenMetadata } from "./interfaces"; diff --git a/src/common/lib/protocol-config.ts b/src/common/lib/protocol-config.ts index c33c8841..8c9ecc2e 100644 --- a/src/common/lib/protocol-config.ts +++ b/src/common/lib/protocol-config.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; export type ProtocolConfig = { basis_points: number; diff --git a/src/common/services/images.ts b/src/common/services/images.ts index 2a843799..28cb41f1 100644 --- a/src/common/services/images.ts +++ b/src/common/services/images.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { Image, socialDbContractClient } from "@/common/contracts/social-db"; type Props = { diff --git a/src/common/wallet/adapters.ts b/src/common/wallet/adapters.ts new file mode 100644 index 00000000..d4cfe3c0 --- /dev/null +++ b/src/common/wallet/adapters.ts @@ -0,0 +1,33 @@ +import { create } from "zustand"; + +import type { AccountId } from "@/common/types"; + +type WalletUserAccountState = { + accountId: AccountId | undefined; + isSignedIn: boolean; +}; + +type WalletUserAdapterState = { error: null | unknown } & ( + | { isReady: false; isSignedIn: false; accountId: undefined } + | ({ isReady: true } & WalletUserAccountState) +); + +const initialWalletUserAdapterState: WalletUserAdapterState = { + isReady: false, + isSignedIn: false, + accountId: undefined, + error: null, +}; + +type WalletUserAdapterStore = WalletUserAdapterState & { + registerInit: (isReady: boolean) => void; + setAccountState: (state: WalletUserAccountState) => void; + setError: (error: unknown) => void; +}; + +export const useWalletUserAdapter = create((set) => ({ + ...initialWalletUserAdapterState, + registerInit: (isReady: boolean) => set(isReady ? { isReady } : initialWalletUserAdapterState), + setAccountState: (newAccountState: WalletUserAccountState) => set(newAccountState), + setError: (error: unknown) => set({ error }), +})); diff --git a/src/common/wallet/adapters/user.ts b/src/common/wallet/adapters/user.ts deleted file mode 100644 index 2f8b16f4..00000000 --- a/src/common/wallet/adapters/user.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { create } from "zustand"; - -import type { AccountId } from "@/common/types"; - -type WalletAccountState = { - accountId: AccountId | undefined; - isSignedIn: boolean; -}; - -type WalletUserAdapterContextState = { error: null | unknown } & ( - | { isReady: false; isSignedIn: false; accountId: undefined } - | ({ isReady: true } & WalletAccountState) -); - -const initialWalletUserAdapterContextState: WalletUserAdapterContextState = { - isReady: false, - isSignedIn: false, - accountId: undefined, - error: null, -}; - -type WalletUserAdapterContextStore = WalletUserAdapterContextState & { - registerInit: (isReady: boolean) => void; - setAccountState: (state: WalletAccountState) => void; - setError: (error: unknown) => void; -}; - -export const useWalletUserAdapterContext = create((set) => ({ - ...initialWalletUserAdapterContextState, - - registerInit: (isReady: boolean) => - set(isReady ? { isReady } : initialWalletUserAdapterContextState), - - setAccountState: (newAccountState: WalletAccountState) => set(newAccountState), - setError: (error: unknown) => set({ error }), -})); diff --git a/src/common/wallet/components.tsx b/src/common/wallet/components.tsx index 82869b2d..1a5dc2e6 100644 --- a/src/common/wallet/components.tsx +++ b/src/common/wallet/components.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect } from "react"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { IS_CLIENT } from "@/common/constants"; -import { useWalletUserAdapterContext } from "./adapters/user"; +import { useWalletUserAdapter } from "./adapters"; type WalletProviderProps = { children: React.ReactNode; @@ -11,7 +11,7 @@ type WalletProviderProps = { const WalletProvider: React.FC = ({ children }) => { const { registerInit, setAccountState, setError, isReady, isSignedIn, accountId, error } = - useWalletUserAdapterContext(); + useWalletUserAdapter(); const syncWalletState = useCallback(() => { const isWalletSignedIn = nearProtocolClient.walletApi.walletSelector.isSignedIn(); diff --git a/src/common/wallet/hooks/session.ts b/src/common/wallet/hooks.ts similarity index 94% rename from src/common/wallet/hooks/session.ts rename to src/common/wallet/hooks.ts index f64c4ce2..8075db2f 100644 --- a/src/common/wallet/hooks/session.ts +++ b/src/common/wallet/hooks.ts @@ -8,11 +8,11 @@ import { sybilResistanceContractHooks } from "@/common/contracts/core/sybil-resi import { isAccountId } from "@/common/lib"; import { useGlobalStoreSelector } from "@/store"; -import { useWalletUserAdapterContext } from "../adapters/user"; -import { WalletUserSession } from "../types"; +import { useWalletUserAdapter } from "./adapters"; +import { WalletUserSession } from "./types"; export const useWalletUserSession = (): WalletUserSession => { - const wallet = useWalletUserAdapterContext(); + const wallet = useWalletUserAdapter(); const { actAsDao } = useGlobalStoreSelector(prop("nav")); const daoAccountId = actAsDao.defaultAddress; const isDaoAccountIdValid = useMemo(() => isAccountId(daoAccountId), [daoAccountId]); diff --git a/src/common/wallet/index.ts b/src/common/wallet/index.ts index eedfb25b..777db88c 100644 --- a/src/common/wallet/index.ts +++ b/src/common/wallet/index.ts @@ -1,3 +1,3 @@ export * from "./components"; -export * from "./hooks/session"; +export * from "./hooks"; export * from "./types"; diff --git a/src/common/wallet/internal/wallet-provider.tsx b/src/common/wallet/internal/wallet-provider.tsx deleted file mode 100644 index fcb8e5a1..00000000 --- a/src/common/wallet/internal/wallet-provider.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useCallback, useEffect } from "react"; - -import { nearProtocolClient } from "@/common/api/near-protocol"; - -import { useWalletUserAdapterContext } from "../adapters/user"; - -export type WalletProviderProps = { - children: React.ReactNode; -}; - -export const WalletProvider: React.FC = ({ children }) => { - const { registerInit, setAccountState, setError, isReady, isSignedIn, accountId, error } = - useWalletUserAdapterContext(); - - const syncWalletState = useCallback(() => { - const isWalletSignedIn = nearProtocolClient.walletApi.walletSelector.isSignedIn(); - const walletAccountId = nearProtocolClient.walletApi.accountId; - - if (isWalletSignedIn !== isSignedIn || walletAccountId !== accountId) { - setAccountState({ accountId: walletAccountId, isSignedIn: isWalletSignedIn }); - } - }, [accountId, isSignedIn, setAccountState]); - - useEffect(() => { - if (!isReady && error === null) { - nearProtocolClient.walletApi - .initNear() - .then(() => registerInit(true)) - .catch((error) => { - console.log(error); - setError(error); - }); - } - }, [error, isReady, registerInit, setError]); - - useEffect(() => { - if (isReady) { - syncWalletState(); - - nearProtocolClient.walletApi.walletSelector.on("signedIn", syncWalletState); - nearProtocolClient.walletApi.walletSelector.on("signedOut", syncWalletState); - nearProtocolClient.walletApi.walletSelector.on("accountsChanged", syncWalletState); - nearProtocolClient.walletApi.walletSelector.on("networkChanged", syncWalletState); - nearProtocolClient.walletApi.walletSelector.on("uriChanged", syncWalletState); - } - - return () => { - if (isReady) { - nearProtocolClient.walletApi.walletSelector.off("signedIn", syncWalletState); - nearProtocolClient.walletApi.walletSelector.off("signedOut", syncWalletState); - nearProtocolClient.walletApi.walletSelector.off("accountsChanged", syncWalletState); - nearProtocolClient.walletApi.walletSelector.off("networkChanged", syncWalletState); - nearProtocolClient.walletApi.walletSelector.off("uriChanged", syncWalletState); - } - }; - }, [syncWalletState, isReady]); - - return children; -}; diff --git a/src/entities/_shared/account/model/schemas.ts b/src/entities/_shared/account/model/schemas.ts index 350e4a8b..fa676451 100644 --- a/src/entities/_shared/account/model/schemas.ts +++ b/src/entities/_shared/account/model/schemas.ts @@ -2,7 +2,7 @@ import { AccountView } from "near-api-js/lib/providers/provider"; import { string } from "zod"; import { NETWORK } from "@/common/_config"; -import { near, nearRpc } from "@/common/api/near-protocol/client"; +import { near, nearRpc } from "@/common/blockchains/near-protocol/client"; const primitive = string().min(5, "Account ID is too short"); diff --git a/src/entities/_shared/token/hooks/data.ts b/src/entities/_shared/token/hooks/data.ts index c5d066b4..09837bcd 100644 --- a/src/entities/_shared/token/hooks/data.ts +++ b/src/entities/_shared/token/hooks/data.ts @@ -4,7 +4,7 @@ import { Big } from "big.js"; import { coingeckoHooks } from "@/common/api/coingecko"; import { intearTokenIndexerHooks } from "@/common/api/intear-token-indexer"; -import { nearHooks } from "@/common/api/near-protocol"; +import { nearProtocolHooks } from "@/common/blockchains/near-protocol"; import { NATIVE_TOKEN_ID, PLATFORM_LISTED_TOKEN_IDS } from "@/common/constants"; import { refExchangeContractHooks } from "@/common/contracts/ref-finance"; import { ftContractHooks } from "@/common/contracts/tokens"; @@ -59,7 +59,7 @@ export const useToken = ({ isLoading: isNtMetadataLoading, data: ntMetadata, error: ntMetadataError, - } = nearHooks.useNativeTokenMetadata({ + } = nearProtocolHooks.useNativeTokenMetadata({ disabled: !enabled || tokenId !== NATIVE_TOKEN_ID, }); @@ -75,7 +75,7 @@ export const useToken = ({ isLoading: isAccountSummaryLoading, data: accountSummary, error: accountSummaryError, - } = nearHooks.useViewAccount({ + } = nearProtocolHooks.useViewAccount({ disabled: !enabled || balanceCheckAccountId === undefined || tokenId !== NATIVE_TOKEN_ID, accountId: balanceCheckAccountId as AccountId, }); diff --git a/src/entities/_shared/token/types.ts b/src/entities/_shared/token/types.ts index a67ba6fa..3b8e4d7f 100644 --- a/src/entities/_shared/token/types.ts +++ b/src/entities/_shared/token/types.ts @@ -1,4 +1,4 @@ -import type { NativeTokenMetadata } from "@/common/api/near-protocol/hooks"; +import type { NativeTokenMetadata } from "@/common/blockchains/near-protocol/hooks"; import type { FungibleTokenMetadata } from "@/common/contracts/tokens"; import type { AccountId, ByTokenId } from "@/common/types"; diff --git a/src/entities/campaign/models/effects.ts b/src/entities/campaign/models/effects.ts index cd8605c3..8f37ece7 100644 --- a/src/entities/campaign/models/effects.ts +++ b/src/entities/campaign/models/effects.ts @@ -1,6 +1,6 @@ import { ExecutionStatus, ExecutionStatusBasic } from "near-api-js/lib/providers/provider"; -import { nearRpc, walletApi } from "@/common/api/near-protocol/client"; +import { nearRpc, walletApi } from "@/common/blockchains/near-protocol/client"; import { AppDispatcher } from "@/store"; import { CampaignEnumType } from "../types"; diff --git a/src/entities/campaign/models/schema.ts b/src/entities/campaign/models/schema.ts index cd56279a..7a50e367 100644 --- a/src/entities/campaign/models/schema.ts +++ b/src/entities/campaign/models/schema.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { NETWORK } from "@/common/_config"; -import { near } from "@/common/api/near-protocol/client"; +import { near } from "@/common/blockchains/near-protocol/client"; export const campaignFormSchema = z .object({ diff --git a/src/entities/dao/utils/validation.ts b/src/entities/dao/utils/validation.ts index 54eb06c5..09290f9b 100644 --- a/src/entities/dao/utils/validation.ts +++ b/src/entities/dao/utils/validation.ts @@ -1,4 +1,4 @@ -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; type Role = { name: string; diff --git a/src/entities/list/components/AccountCard.tsx b/src/entities/list/components/AccountCard.tsx index abcd21e6..fec2bd42 100644 --- a/src/entities/list/components/AccountCard.tsx +++ b/src/entities/list/components/AccountCard.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; import { ListRegistration } from "@/common/api/indexer"; -import { walletApi } from "@/common/api/near-protocol/client"; +import { walletApi } from "@/common/blockchains/near-protocol/client"; import { RegistrationStatus, listsContractClient } from "@/common/contracts/core/lists"; import { truncate } from "@/common/lib"; import { diff --git a/src/entities/list/components/ListHero.tsx b/src/entities/list/components/ListHero.tsx index 16668e6c..36baf04a 100644 --- a/src/entities/list/components/ListHero.tsx +++ b/src/entities/list/components/ListHero.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { useRouter } from "next/navigation"; -import { walletApi } from "@/common/api/near-protocol/client"; +import { walletApi } from "@/common/blockchains/near-protocol/client"; import { Button } from "@/common/ui/components"; import { useAllLists } from "@/entities/list/hooks/useAllLists"; diff --git a/src/entities/list/hooks/useListForm.ts b/src/entities/list/hooks/useListForm.ts index f0184493..8844830c 100644 --- a/src/entities/list/hooks/useListForm.ts +++ b/src/entities/list/hooks/useListForm.ts @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; import { prop } from "remeda"; import { LISTS_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { listsContractClient } from "@/common/contracts/core/lists"; import { floatToYoctoNear } from "@/common/lib"; import { AccountId } from "@/common/types"; diff --git a/src/entities/list/models/effects.ts b/src/entities/list/models/effects.ts index 64f93bbc..2b3aad98 100644 --- a/src/entities/list/models/effects.ts +++ b/src/entities/list/models/effects.ts @@ -1,7 +1,7 @@ import { ExecutionStatusBasic } from "near-api-js/lib/providers/provider"; import { List } from "@/common/api/indexer"; -import { nearRpc, walletApi } from "@/common/api/near-protocol/client"; +import { nearRpc, walletApi } from "@/common/blockchains/near-protocol/client"; import { AppDispatcher } from "@/store"; import { ListFormModalType } from "../types"; diff --git a/src/entities/pot/hooks/forms.ts b/src/entities/pot/hooks/forms.ts index 542675fd..b09e8973 100644 --- a/src/entities/pot/hooks/forms.ts +++ b/src/entities/pot/hooks/forms.ts @@ -6,7 +6,7 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FULL_TGAS } from "@/common/constants"; import { potContractClient } from "@/common/contracts/core/pot"; diff --git a/src/features/donation/components/DonationModal.tsx b/src/features/donation/components/DonationModal.tsx index 0be10c0a..d5ac6fca 100644 --- a/src/features/donation/components/DonationModal.tsx +++ b/src/features/donation/components/DonationModal.tsx @@ -2,7 +2,7 @@ import { useCallback } from "react"; import { create, useModal } from "@ebay/nice-modal-react"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { useRouteQuery } from "@/common/lib"; import { Button, Dialog, DialogContent, ModalErrorBody } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; diff --git a/src/features/donation/models/effects.ts b/src/features/donation/models/effects.ts index 7b746c31..3abb4275 100644 --- a/src/features/donation/models/effects.ts +++ b/src/features/donation/models/effects.ts @@ -3,7 +3,7 @@ import axios from "axios"; import { Big } from "big.js"; import { DONATION_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { RPC_NODE_URL, naxiosInstance, walletApi } from "@/common/api/near-protocol/client"; +import { RPC_NODE_URL, naxiosInstance, walletApi } from "@/common/blockchains/near-protocol/client"; import { FULL_TGAS, NATIVE_TOKEN_DECIMALS, NATIVE_TOKEN_ID } from "@/common/constants"; import { type CampaignDonation, diff --git a/src/features/matching-pool-contribution/hooks/forms.ts b/src/features/matching-pool-contribution/hooks/forms.ts index 78badda5..cd65c9f8 100644 --- a/src/features/matching-pool-contribution/hooks/forms.ts +++ b/src/features/matching-pool-contribution/hooks/forms.ts @@ -5,7 +5,7 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FIFTY_TGAS, FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK, ONE_TGAS } from "@/common/constants"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; diff --git a/src/features/pot-application/hooks/forms.ts b/src/features/pot-application/hooks/forms.ts index 23daffb4..f67a929c 100644 --- a/src/features/pot-application/hooks/forms.ts +++ b/src/features/pot-application/hooks/forms.ts @@ -6,7 +6,7 @@ import { parseNearAmount } from "near-api-js/lib/utils/format"; import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK, ONE_TGAS } from "@/common/constants"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; diff --git a/src/features/pot-configuration/model/effects.ts b/src/features/pot-configuration/model/effects.ts index 9c9154ee..086c03c1 100644 --- a/src/features/pot-configuration/model/effects.ts +++ b/src/features/pot-configuration/model/effects.ts @@ -2,7 +2,7 @@ import { ExecutionStatusBasic } from "near-api-js/lib/providers/provider"; import { omit } from "remeda"; import { ByPotId } from "@/common/api/indexer"; -import { nearRpc, walletApi } from "@/common/api/near-protocol/client"; +import { nearRpc, walletApi } from "@/common/blockchains/near-protocol/client"; import { type PotConfig, potContractClient } from "@/common/contracts/core/pot"; import { type PotDeploymentResult, diff --git a/src/features/profile-setup/models/effects.ts b/src/features/profile-setup/models/effects.ts index 55586372..7c07a118 100644 --- a/src/features/profile-setup/models/effects.ts +++ b/src/features/profile-setup/models/effects.ts @@ -3,7 +3,7 @@ import { Big } from "big.js"; import { parseNearAmount } from "near-api-js/lib/utils/format"; import { LISTS_CONTRACT_ACCOUNT_ID, SOCIAL_DB_CONTRACT_ACCOUNT_ID } from "@/common/_config"; -import { naxiosInstance } from "@/common/api/near-protocol/client"; +import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FIFTY_TGAS, FULL_TGAS, diff --git a/src/layout/components/app-bar.tsx b/src/layout/components/app-bar.tsx index 6ec5bb51..84c7e4fd 100644 --- a/src/layout/components/app-bar.tsx +++ b/src/layout/components/app-bar.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { NETWORK } from "@/common/_config"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { Button, Skeleton } from "@/common/ui/components"; import { cn } from "@/common/ui/utils"; import { useWalletUserSession } from "@/common/wallet"; diff --git a/src/layout/components/user-dropdown.tsx b/src/layout/components/user-dropdown.tsx index df90554e..1040abe8 100644 --- a/src/layout/components/user-dropdown.tsx +++ b/src/layout/components/user-dropdown.tsx @@ -4,7 +4,7 @@ import { LogOut } from "lucide-react"; import Link from "next/link"; import { LazyLoadImage } from "react-lazy-load-image-component"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { truncate } from "@/common/lib"; import { Button, diff --git a/src/pages/pot/[potId]/votes.tsx b/src/pages/pot/[potId]/votes.tsx index aada06e7..3ee84410 100644 --- a/src/pages/pot/[potId]/votes.tsx +++ b/src/pages/pot/[potId]/votes.tsx @@ -10,7 +10,7 @@ import { MdStar, } from "react-icons/md"; -import { nearProtocolClient } from "@/common/api/near-protocol"; +import { nearProtocolClient } from "@/common/blockchains/near-protocol"; import { votingContractHooks } from "@/common/contracts/core/voting"; import { isAccountId } from "@/common/lib"; import type { AccountId } from "@/common/types"; From c71ba96e7d24660a4bad46d210c977a7c1c287cf Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Fri, 31 Jan 2025 19:57:31 +0000 Subject: [PATCH 75/84] Decouple status types --- src/common/contracts/core/pot/interfaces.ts | 1 + src/entities/_shared/account/constants.ts | 10 ++ src/entities/_shared/account/types.ts | 25 +++++ src/entities/list/components/AccountCard.tsx | 18 ++-- src/entities/list/components/ListAccounts.tsx | 4 +- src/entities/list/hooks/registrations.ts | 7 +- src/entities/project/constants.ts | 10 -- src/entities/project/index.ts | 3 - src/entities/project/types.ts | 13 --- src/layout/components/project-discovery.tsx | 5 +- src/pages/pot/[potId]/applications.tsx | 96 ++++++++++--------- 11 files changed, 105 insertions(+), 87 deletions(-) delete mode 100644 src/entities/project/constants.ts delete mode 100644 src/entities/project/index.ts delete mode 100644 src/entities/project/types.ts diff --git a/src/common/contracts/core/pot/interfaces.ts b/src/common/contracts/core/pot/interfaces.ts index bce6040f..ae5ac6b3 100644 --- a/src/common/contracts/core/pot/interfaces.ts +++ b/src/common/contracts/core/pot/interfaces.ts @@ -4,6 +4,7 @@ export enum ApplicationStatus { Pending = "Pending", Approved = "Approved", Rejected = "Rejected", + InReview = "InReview", } export enum Roles { diff --git a/src/entities/_shared/account/constants.ts b/src/entities/_shared/account/constants.ts index 2dd93597..4d400c26 100644 --- a/src/entities/_shared/account/constants.ts +++ b/src/entities/_shared/account/constants.ts @@ -4,6 +4,7 @@ import { RegistrationStatus } from "@/common/contracts/core/lists"; import { type AccountCategoryOption, type AccountCategoryVariant, + type AccountListRegistrationStatusOption, type AccountProfileLinktreeKey, } from "./types"; @@ -98,3 +99,12 @@ export const ACCOUNT_REGISTRATION_STATUSES: Record< toggleColor: "#C7C7C7", }, }; + +export const ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS: AccountListRegistrationStatusOption[] = [ + { label: "All", val: "All" }, + { label: "Approved", val: "Approved" }, + { label: "Pending", val: "Pending" }, + { label: "Rejected", val: "Rejected" }, + { label: "Graylisted", val: "Graylisted" }, + { label: "Blacklisted", val: "Blacklisted" }, +]; diff --git a/src/entities/_shared/account/types.ts b/src/entities/_shared/account/types.ts index 36e79d50..3cd8d8c7 100644 --- a/src/entities/_shared/account/types.ts +++ b/src/entities/_shared/account/types.ts @@ -1,4 +1,6 @@ import type { Account } from "@/common/api/indexer"; +import type { RegistrationStatus } from "@/common/contracts/core/lists"; +import type { ApplicationStatus } from "@/common/contracts/core/pot"; import type { ProfileLinktree } from "@/common/contracts/social-db"; import { ByAccountId, ByRegistrationId } from "@/common/types"; @@ -37,3 +39,26 @@ export type AccountCategoryOption = { }; export type AccountSnapshot = Account; + +export type AccountListRegistrationStatus = keyof typeof RegistrationStatus; + +export type AccountListRegistrationStatusVariant = + | AccountListRegistrationStatus + /** + *? INFO: Only needed for backward compatibility: + */ + | "All"; + +export type AccountListRegistrationStatusOption = { + label: string; + val: AccountListRegistrationStatusVariant; +}; + +export type AccountPotApplicationStatus = keyof typeof ApplicationStatus; + +export type AccountPotApplicationStatusVariant = AccountPotApplicationStatus | "All"; + +export type AccountPotApplicationStatusOption = { + label: string; + val: AccountPotApplicationStatusVariant; +}; diff --git a/src/entities/list/components/AccountCard.tsx b/src/entities/list/components/AccountCard.tsx index fec2bd42..985b5508 100644 --- a/src/entities/list/components/AccountCard.tsx +++ b/src/entities/list/components/AccountCard.tsx @@ -27,11 +27,11 @@ import DownArrow from "@/common/ui/svg/DownArrow"; import { ListNoteIcon } from "@/common/ui/svg/list-note"; import { cn } from "@/common/ui/utils"; import { + ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS, AccountHandle, AccountProfileCover, AccountProfilePicture, -} from "@/entities/_shared/account"; -import { statuses } from "@/entities/project/constants"; +} from "@/entities/_shared"; import { dispatch } from "@/store"; import { listRegistrationStatuses } from "../constants"; @@ -183,13 +183,13 @@ export const AccountCard = ({
- {statuses - .filter((item) => item.val !== "All") - .map((item) => ( - - {item.val} - - ))} + {ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS.filter( + (item) => item.val !== "All", + ).map((item) => ( + + {item.val} + + ))} ) : ( diff --git a/src/entities/list/components/ListAccounts.tsx b/src/entities/list/components/ListAccounts.tsx index 812d84fe..c47cd128 100644 --- a/src/entities/list/components/ListAccounts.tsx +++ b/src/entities/list/components/ListAccounts.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react"; import { List } from "@/common/api/indexer"; import { Filter, Group, GroupType, SearchBar, SortSelect } from "@/common/ui/components"; -import { statuses } from "@/entities/project/constants"; +import { ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS } from "@/entities/_shared"; import { AccountCard } from "./AccountCard"; import { ListCardSkeleton } from "./ListCardSkeleton"; @@ -40,7 +40,7 @@ export const ListAccounts = ({ const tagsList: Group[] = [ { label: "Status", - options: statuses, + options: ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS, type: GroupType.single, props: { value: statusFilter, diff --git a/src/entities/list/hooks/registrations.ts b/src/entities/list/hooks/registrations.ts index a6ff534e..93208656 100644 --- a/src/entities/list/hooks/registrations.ts +++ b/src/entities/list/hooks/registrations.ts @@ -3,9 +3,7 @@ import { useCallback, useMemo, useState } from "react"; import { indexer } from "@/common/api/indexer"; import { oldToRecent } from "@/common/lib"; import { ByListId, ChronologicalSortOrder, ChronologicalSortOrderVariant } from "@/common/types"; -import type { AccountCategory } from "@/entities/_shared/account"; - -import { ProjectListingStatusVariant } from "../../project/types"; +import { AccountCategory, type AccountListRegistrationStatusVariant } from "@/entities/_shared"; export type ListRegistrationLookupParams = ByListId & {}; @@ -14,7 +12,8 @@ export const useListRegistrationLookup = ({ listId }: ListRegistrationLookupPara const [searchTerm, setSearchTerm] = useState(undefined); const [categoryFilter, setCategoryFilter] = useState([]); - const [statusFilter, setStatusFilter] = useState("Approved"); + const [statusFilter, setStatusFilter] = + useState("Approved"); const [sortingOrder, setSortingOrder] = useState( ChronologicalSortOrder.recent, diff --git a/src/entities/project/constants.ts b/src/entities/project/constants.ts deleted file mode 100644 index d8fe493c..00000000 --- a/src/entities/project/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ProjectListingStatusOption } from "./types"; - -export const statuses: ProjectListingStatusOption[] = [ - { label: "All", val: "All" }, - { label: "Approved", val: "Approved" }, - { label: "Pending", val: "Pending" }, - { label: "Rejected", val: "Rejected" }, - { label: "Graylisted", val: "Graylisted" }, - { label: "Blacklisted", val: "Blacklisted" }, -]; diff --git a/src/entities/project/index.ts b/src/entities/project/index.ts deleted file mode 100644 index fffe6138..00000000 --- a/src/entities/project/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./constants"; - -export * from "./types"; diff --git a/src/entities/project/types.ts b/src/entities/project/types.ts deleted file mode 100644 index ca842c9d..00000000 --- a/src/entities/project/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ListRegistrationStatus } from "@/common/api/indexer"; - -export type ProjectListingStatusVariant = - | ListRegistrationStatus - /** - *? INFO: Only needed for backward compatibility: - */ - | "All"; - -export type ProjectListingStatusOption = { - label: string; - val: ProjectListingStatusVariant; -}; diff --git a/src/layout/components/project-discovery.tsx b/src/layout/components/project-discovery.tsx index 5bdd4bcf..fe12ac15 100644 --- a/src/layout/components/project-discovery.tsx +++ b/src/layout/components/project-discovery.tsx @@ -19,14 +19,13 @@ import { import { cn } from "@/common/ui/utils"; import { ACCOUNT_CATEGORY_OPTIONS, + ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS, AccountCard, AccountCardSkeleton, } from "@/entities/_shared/account"; import { useListRegistrationLookup } from "@/entities/list"; import { DonateToAccountButton } from "@/features/donation"; -import { statuses } from "../../entities/project/constants"; - const ListRegistrationLookupPlaceholder = () => Array.from({ length: 6 }, (_, index) => ); @@ -68,7 +67,7 @@ export const ProjectDiscovery: React.FC = ({ { label: "Status", - options: statuses, + options: ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS, type: GroupType.single, props: { diff --git a/src/pages/pot/[potId]/applications.tsx b/src/pages/pot/[potId]/applications.tsx index 0ef43233..ad77244f 100644 --- a/src/pages/pot/[potId]/applications.tsx +++ b/src/pages/pot/[potId]/applications.tsx @@ -1,7 +1,8 @@ -import { ReactElement, useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; +import { entries } from "remeda"; import { styled } from "styled-components"; import { PotApplication, indexer } from "@/common/api/indexer"; @@ -9,7 +10,10 @@ import { usePot } from "@/common/api/indexer/hooks"; import { oldToRecent } from "@/common/lib"; import type { AccountId } from "@/common/types"; import { FilterChip, SearchBar } from "@/common/ui/components"; -import { ProjectListingStatusVariant } from "@/entities/project"; +import { + type AccountPotApplicationStatusOption, + type AccountPotApplicationStatusVariant, +} from "@/entities/_shared"; import { PotApplicationCard, PotApplicationCardSkeleton, @@ -39,7 +43,7 @@ const ApplicationLookupPlaceholder = () => Array.from({ length: 6 }, (_, i) => ); // TODO: Apply optimizations -const ApplicationsTab = () => { +export default function ApplicationsTab() { const router = useRouter(); const { potId } = router.query as { @@ -55,7 +59,7 @@ const ApplicationsTab = () => { const owner = potDetail?.owner?.id || ""; const admins = potDetail?.admins.map((adm) => adm.id) || []; const chef = potDetail?.chef?.id || ""; - const [statusFilter, setStatusFilter] = useState("All"); + const [statusFilter, setStatusFilter] = useState("All"); const [pageNumber, setPageNumber] = useState(1); const [searchTerm, setSearchTerm] = useState(undefined); @@ -97,35 +101,45 @@ const ApplicationsTab = () => { setProjectStatus(""); }; - const getApplicationCount = (status: string) => { - return applications?.results.filter((app) => app.status === status).length; - }; - - const applicationsFilters: Record = { - All: { - label: "All", - val: "all", - count: applications?.count, - }, - - Approved: { - label: "Approved", - val: "approved", - count: getApplicationCount("Approved")!, - }, - - Pending: { - label: "Pending", - val: "pending", - count: getApplicationCount("Pending")!, - }, - - Rejected: { - label: "Rejected", - val: "rejected", - count: getApplicationCount("Rejected")!, - }, - }; + const applicationsFilters: Record< + AccountPotApplicationStatusVariant, + AccountPotApplicationStatusOption & { count?: number } + > = useMemo(() => { + const getApplicationCount = (status: AccountPotApplicationStatusVariant) => + applications?.results.filter((application) => application.status === status).length ?? 0; + + return { + All: { + label: "All", + val: "All", + count: applications?.count, + }, + + Approved: { + label: "Approved", + val: "Approved", + count: getApplicationCount("Approved"), + }, + + Pending: { + label: "Pending", + val: "Pending", + count: getApplicationCount("Pending"), + }, + + Rejected: { + label: "Rejected", + val: "Rejected", + count: getApplicationCount("Rejected"), + }, + + InReview: { + label: "In Review", + val: "InReview", + count: getApplicationCount("InReview"), + }, + }; + }, [applications]); const isChefOrGreater = accountId === chef || admins.includes(accountId || "") || accountId === owner; @@ -152,15 +166,13 @@ const ApplicationsTab = () => { )}
- {Object.keys(applicationsFilters).map((key) => ( + {entries(applicationsFilters).map(([key, filter]) => ( - setStatusFilter(applicationsFilters[key].label as ProjectListingStatusVariant) - } + onClick={() => setStatusFilter(filter.val)} className="font-medium" - label={applicationsFilters[key].label} - count={applicationsFilters[key].count} + label={filter.label} + count={filter.count} key={key} /> ))} @@ -209,10 +221,8 @@ const ApplicationsTab = () => {
); -}; +} -ApplicationsTab.getLayout = function getLayout(page: ReactElement) { +ApplicationsTab.getLayout = function getLayout(page: React.ReactNode) { return {page}; }; - -export default ApplicationsTab; From bef3fb51b5b1bfbeeaf62fd177d37f9517b859f7 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Fri, 31 Jan 2025 20:23:22 +0000 Subject: [PATCH 76/84] wip --- src/common/wallet/hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/wallet/hooks.ts b/src/common/wallet/hooks.ts index 8075db2f..47830aeb 100644 --- a/src/common/wallet/hooks.ts +++ b/src/common/wallet/hooks.ts @@ -33,7 +33,7 @@ export const useWalletUserSession = (): WalletUserSession => { const isMetadataLoading = isHumanVerificationStatusLoading || isRegistrationLoading; - console.log("WALLET in SESSION", wallet); + console.log("wallet in useWalletUserSession", wallet); return useMemo(() => { if (wallet.isReady && wallet.isSignedIn && wallet.accountId) { From 9fdc561ad4ef0550c6313030f944c90c38aaced1 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 3 Feb 2025 11:09:46 +0000 Subject: [PATCH 77/84] Fix pot configuration editor issues --- src/common/lib/datetime.ts | 7 + .../components/templates/page-with-banner.tsx | 2 +- src/features/donation/models/schemas.ts | 9 +- .../components/PotConfigurationEditor.tsx | 664 ++++++++++-------- src/features/pot-configuration/constants.ts | 40 +- src/features/pot-configuration/hooks/forms.ts | 88 ++- .../pot-configuration/utils/normalization.ts | 91 +-- src/layout/pot/components/layout.tsx | 2 +- 8 files changed, 475 insertions(+), 428 deletions(-) diff --git a/src/common/lib/datetime.ts b/src/common/lib/datetime.ts index 3781268f..fe7920f6 100644 --- a/src/common/lib/datetime.ts +++ b/src/common/lib/datetime.ts @@ -1,6 +1,11 @@ import { Temporal } from "temporal-polyfill"; import { number, preprocess } from "zod"; +/** + * 86400000 milliseconds make 24 hours that make 24 * 60 minutes that make 24 * 60 * 60 seconds + */ +export const DAY_IN_MILLISECONDS: 86400000 = (24 * 60 * 60 * 1000) as 86400000; + export const DATETIME_INCORRECT_FORMAT_ERROR = "Incorrect datetime"; export const dropTimezoneIndicator = (value: string): string => value.slice(0, 16); @@ -97,3 +102,5 @@ export const futureTimestamp = timestamp.refine( (value) => value > Temporal.Now.instant().epochMilliseconds, { message: "Cannot be in the past" }, ); + +export const daysFloatToMilliseconds = (daysFloat: number) => daysFloat * DAY_IN_MILLISECONDS; diff --git a/src/common/ui/components/templates/page-with-banner.tsx b/src/common/ui/components/templates/page-with-banner.tsx index d9ed02d5..34747f0f 100644 --- a/src/common/ui/components/templates/page-with-banner.tsx +++ b/src/common/ui/components/templates/page-with-banner.tsx @@ -3,5 +3,5 @@ export type PageWithBannerProps = { }; export const PageWithBanner: React.FC = ({ children }) => ( -
{children}
+
{children}
); diff --git a/src/features/donation/models/schemas.ts b/src/features/donation/models/schemas.ts index 6d1c0339..c0a550f3 100644 --- a/src/features/donation/models/schemas.ts +++ b/src/features/donation/models/schemas.ts @@ -36,11 +36,12 @@ export const donationAmount = safePositiveNumber; */ export const donationFee = preprocess( (value) => (typeof value === "string" ? safePositiveNumber.parse(value) : value), - safePositiveNumber, -).refine((percents) => percents < 100, { - message: `Fee must be less than 100%.`, -}); +) + .refine((percents) => percents < 100, { message: "Fee must be less than 100%." }) + .refine((percents) => Number.isInteger(percents), { + message: "Fractional percentage is not supported.", + }); export const donationSchema = object({ tokenId: donationTokenSchema, diff --git a/src/features/pot-configuration/components/PotConfigurationEditor.tsx b/src/features/pot-configuration/components/PotConfigurationEditor.tsx index 1d146028..364b97d0 100644 --- a/src/features/pot-configuration/components/PotConfigurationEditor.tsx +++ b/src/features/pot-configuration/components/PotConfigurationEditor.tsx @@ -1,6 +1,7 @@ import { useCallback, useMemo, useState } from "react"; import { useRouter } from "next/router"; +import { Temporal } from "temporal-polyfill"; import { ByPotId, type PotId, indexer } from "@/common/api/indexer"; import { NATIVE_TOKEN_ID } from "@/common/constants"; @@ -41,6 +42,11 @@ export const PotConfigurationEditor: React.FC = ({ }) => { const router = useRouter(); const isNewPot = typeof potId !== "string"; + const [isInPreviewMode, setPreviewMode] = useState(!isNewPot); + const enterEditMode = useCallback(() => setPreviewMode(false), []); + const exitEditMode = useCallback(() => setPreviewMode(true), []); + + // TODO: Get "protocol fee" from the donation contract config via hook const { data: pot } = indexer.usePot({ enabled: !isNewPot, @@ -49,17 +55,13 @@ export const PotConfigurationEditor: React.FC = ({ const schema = useMemo( () => (isNewPot ? getPotDeploymentSchema() : getPotSettingsSchema(pot)), - [isNewPot, pot], ); - const { form, values, handleAdminsUpdate, isDisabled, onSubmit } = usePotConfigurationEditorForm( - isNewPot ? { schema } : { potId, schema }, - ); + const { form, handleAdminsUpdate, isDisabled, isHydrating, onSubmit } = + usePotConfigurationEditorForm(isNewPot ? { schema } : { potId, schema }); - const [isInPreviewMode, setPreviewMode] = useState(!isNewPot); - const enterEditMode = useCallback(() => setPreviewMode(false), []); - const exitEditMode = useCallback(() => setPreviewMode(true), []); + const values = form.watch(); const onCancelClick = useCallback(() => { form.reset(); @@ -72,336 +74,374 @@ export const PotConfigurationEditor: React.FC = ({ return isInPreviewMode ? ( ) : ( -
- -

{!isNewPot && "Edit Pot Settings"}

- -
- - ({ accountId: admin })) ?? []} - onSubmit={handleAdminsUpdate} - /> - - - -
- ( - + {isHydrating ? null : ( + + +

+ {!isNewPot && "Edit Pot Settings"} +

+ +
+ + ({ accountId: admin })) ?? []} + onSubmit={handleAdminsUpdate} + /> + + + +
+ ( + + )} /> - )} - /> - {isNewPot && ( + {isNewPot && ( + ( + + )} + /> + )} +
+ ( - )} /> - )} -
- ( - - )} - /> - -
- ( - - )} - /> - - ( - + ( + + )} /> - )} - /> -
- - - {"Protocol fee is 2%"} - - {"This fee is fixed by the platform"} - - -
- ( - ( + + )} /> - )} - /> - - ( - + + + + {"Protocol fee is 2%"} + + {"This fee is fixed by the platform"} + + +
+ ( + + )} /> - )} - /> -
-
- ( - ( + + )} /> - )} - /> - - ( - + +
+ ( + + )} /> - )} - /> -
-
- ( - - - {NATIVE_TOKEN_ID.toUpperCase()} - - - } - type="number" - placeholder="0.00" - min={DONATION_MIN_NEAR_AMOUNT} - step={0.01} - required - classNames={{ root: "lg:w-50% w-full" }} + ( + + )} /> - )} - /> -
- - - -
- ( - + +
+ ( + + + {NATIVE_TOKEN_ID.toUpperCase()} + + + } + type="number" + placeholder="0.00" + min={DONATION_MIN_NEAR_AMOUNT} + step={0.01} + required + classNames={{ root: "lg:w-50% w-full" }} + /> + )} /> - )} - /> - - ( - + + + +
+ ( + + )} /> - )} - /> -
-
- - - ( - ( + + )} + /> +
+ + + + ( + + )} /> - )} - /> - - - - ( - - - {POT_EDITOR_FIELDS.isPgRegistrationRequired.title} - - - - {`${POT_EDITOR_FIELDS.isPgRegistrationRequired.subtitle} + + + + ( + + + {POT_EDITOR_FIELDS.isPgRegistrationRequired.title} + + + + {`${POT_EDITOR_FIELDS.isPgRegistrationRequired.subtitle} (recommended)`} - - - } + + + } + /> + )} /> - )} - /> - - ( - - - {POT_EDITOR_FIELDS.isSybilResistanceEnabled.title} - - - - {`${POT_EDITOR_FIELDS.isSybilResistanceEnabled.subtitle} (recommended)`} - - - } + + ( + + + {POT_EDITOR_FIELDS.isSybilResistanceEnabled.title} + + + + {`${POT_EDITOR_FIELDS.isSybilResistanceEnabled.subtitle} (recommended)`} + + + } + /> + )} /> - )} - /> - - - -
- - - + + + +
+ + + +
+
-
-
- - + + + )} + ); }; diff --git a/src/features/pot-configuration/constants.ts b/src/features/pot-configuration/constants.ts index ea5380b0..a3d0e207 100644 --- a/src/features/pot-configuration/constants.ts +++ b/src/features/pot-configuration/constants.ts @@ -1,29 +1,31 @@ import { Temporal } from "temporal-polyfill"; +import type { Pot } from "@/common/api/indexer"; + import { PotConfigurationParameters } from "./types"; export const POT_DEFAULT_MIN_DATE = Temporal.Now.instant().toString(); export const POT_EDITOR_EXCLUDED_INDEXED_PROPERTIES = [ - "all_paid_out" as const, - "base_currency" as const, - "cooldown_end" as const, - "cooldown_period_ms" as const, - "custom_min_threshold_score" as const, - "custom_sybil_checks" as const, - "deployed_at" as const, - "deployer" as const, - "matching_pool_balance" as const, - "matching_pool_donations_count" as const, - "pot_factory" as const, - "protocol_config_provider" as const, - "public_donations_count" as const, - "source_metadata" as const, - "total_matching_pool" as const, - "total_matching_pool_usd" as const, - "total_public_donations" as const, - "total_public_donations_usd" as const, -]; + "all_paid_out", + "base_currency", + "cooldown_end", + "cooldown_period_ms", + "custom_min_threshold_score", + "custom_sybil_checks", + "deployed_at", + "deployer", + "matching_pool_balance", + "matching_pool_donations_count", + "pot_factory", + "protocol_config_provider", + "public_donations_count", + "source_metadata", + "total_matching_pool", + "total_matching_pool_usd", + "total_public_donations", + "total_public_donations_usd", +] as const satisfies readonly (keyof Pot)[]; export const POT_EDITOR_FIELDS: PotConfigurationParameters = { owner: { index: "owner", title: "Owner" }, diff --git a/src/features/pot-configuration/hooks/forms.ts b/src/features/pot-configuration/hooks/forms.ts index 5c1d3b4c..37069021 100644 --- a/src/features/pot-configuration/hooks/forms.ts +++ b/src/features/pot-configuration/hooks/forms.ts @@ -3,11 +3,14 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/router"; import { FieldErrors, SubmitHandler, useForm, useWatch } from "react-hook-form"; +import { isDeepEqual, keys, pick } from "remeda"; +import { Temporal } from "temporal-polyfill"; import { infer as FromSchema, ZodError } from "zod"; import { CONTRACT_SOURCECODE_REPO_URL, CONTRACT_SOURCECODE_VERSION } from "@/common/_config"; -import { ByPotId, type PotId, indexer } from "@/common/api/indexer"; -import { PotConfig } from "@/common/contracts/core/pot"; +import { ByPotId, type PotId } from "@/common/api/indexer"; +import { PotConfig, potContractHooks } from "@/common/contracts/core/pot"; +import { daysFloatToMilliseconds } from "@/common/lib"; import { AccountId } from "@/common/types"; import { useWalletUserSession } from "@/common/wallet"; import { PotInputs } from "@/entities/pot"; @@ -23,7 +26,7 @@ import { potDeploymentCrossFieldValidationTargets, potSettingsCrossFieldValidationTargets, } from "../model"; -import { potConfigToSettings, potIndexedDataToPotInputs } from "../utils/normalization"; +import { potConfigToPotConfigInputs, potConfigToSettings } from "../utils/normalization"; export type PotConfigurationEditorFormArgs = | (ByPotId & { schema: PotDeploymentSchema }) @@ -36,9 +39,9 @@ export const usePotConfigurationEditorForm = ({ const viewer = useWalletUserSession(); const router = useRouter(); const potId = "potId" in props ? props.potId : undefined; - const isNewPot = "potId" in props && typeof potId !== "string"; + const isNewPot = potId === undefined; - const { data: potIndexedData } = indexer.usePot({ + const { isLoading: isPotConfigLoading, data: potConfig } = potContractHooks.useConfig({ enabled: potId !== undefined, potId: potId as PotId, }); @@ -49,12 +52,6 @@ export const usePotConfigurationEditorForm = ({ contractMetadata: { latestSourceCodeCommitHash }, } = useCoreState(); - const existingValues = useMemo>( - () => (potIndexedData === undefined ? {} : potIndexedDataToPotInputs(potIndexedData)), - - [potIndexedData], - ); - const defaultValues = useMemo>( () => ({ source_metadata: { @@ -65,16 +62,25 @@ export const usePotConfigurationEditorForm = ({ owner: viewer.accountId, max_projects: 25, - min_matching_pool_donation_amount: 0.1, referral_fee_matching_pool_basis_points: donationFeeBasisPointsToPercents(100), referral_fee_public_round_basis_points: donationFeeBasisPointsToPercents(100), + + application_start_ms: Temporal.Now.instant().epochMilliseconds + daysFloatToMilliseconds(1), + application_end_ms: Temporal.Now.instant().epochMilliseconds + daysFloatToMilliseconds(15), + + public_round_start_ms: + Temporal.Now.instant().epochMilliseconds + daysFloatToMilliseconds(16) + 60000, + + public_round_end_ms: + Temporal.Now.instant().epochMilliseconds + daysFloatToMilliseconds(29) + 60000, + chef_fee_basis_points: donationFeeBasisPointsToPercents(100), isPgRegistrationRequired: true, isSybilResistanceEnabled: true, - ...existingValues, + ...(potConfig === undefined ? {} : potConfigToPotConfigInputs(potConfig)), }), - [existingValues, latestSourceCodeCommitHash, viewer.accountId], + [latestSourceCodeCommitHash, potConfig, viewer.accountId], ); const self = useForm({ @@ -86,6 +92,35 @@ export const usePotConfigurationEditorForm = ({ const values = useWatch(self); + const isHydrating = useMemo(() => isPotConfigLoading, [isPotConfigLoading]); + + const isUnpopulated = + !isDeepEqual(defaultValues, pick(self.formState.defaultValues ?? {}, keys(defaultValues))) && + !self.formState.isDirty; + + useEffect(() => { + if (isNewPot && values.owner === undefined && viewer.hasWalletReady && viewer.isSignedIn) { + self.setValue("owner", viewer.accountId, { shouldValidate: true }); + console.log("test??"); + } + + if (!isNewPot && potConfig !== undefined && isUnpopulated && !isHydrating) { + self.reset(defaultValues, { keepDirty: false, keepIsValid: false }); + } + }, [ + defaultValues, + isHydrating, + isNewPot, + isUnpopulated, + potConfig, + self, + values, + viewer, + viewer.accountId, + viewer.hasWalletReady, + viewer.isSignedIn, + ]); + const handleAdminsUpdate = useCallback( (accountIds: AccountId[]) => self.setValue("admins", accountIds, { shouldDirty: true }), [self], @@ -123,9 +158,11 @@ export const usePotConfigurationEditorForm = ({ values.source_metadata === null || !self.formState.isDirty || !self.formState.isValid || - self.formState.isSubmitting, + self.formState.isSubmitting || + isHydrating, [ + isHydrating, self.formState.isDirty, self.formState.isSubmitting, self.formState.isValid, @@ -134,30 +171,37 @@ export const usePotConfigurationEditorForm = ({ ); const onSubmit: SubmitHandler = useCallback( - (values) => + (values) => { + self.trigger(); + dispatch.potConfiguration.save({ onDeploymentSuccess: ({ potId }: ByPotId) => router.push(`${rootPathnames.pot}/${potId}`), - onUpdate: (config: PotConfig) => self.reset(potConfigToSettings(config)), - ...(isNewPot ? (values as PotDeploymentInputs) : { potId, ...(values as PotSettings) }), - }), + }); + }, [isNewPot, potId, router, self], ); - return { - form: { + const formWithCrossFieldErrors = useMemo( + () => ({ ...self, formState: { ...self.formState, errors: { ...self.formState.errors, ...crossFieldErrors }, }, - }, + }), + [crossFieldErrors, self], + ); + + return { + form: formWithCrossFieldErrors, handleAdminsUpdate, isDisabled, + isHydrating, isNewPot, onSubmit: self.handleSubmit(onSubmit), values, diff --git a/src/features/pot-configuration/utils/normalization.ts b/src/features/pot-configuration/utils/normalization.ts index 43ab92ad..dd9cb879 100644 --- a/src/features/pot-configuration/utils/normalization.ts +++ b/src/features/pot-configuration/utils/normalization.ts @@ -1,4 +1,4 @@ -import { conditional, evolve, isNonNullish, omit, piped, prop } from "remeda"; +import { conditional, evolve, isNonNullish, piped } from "remeda"; import { Temporal } from "temporal-polyfill"; import { LISTS_CONTRACT_ACCOUNT_ID, SYBIL_CONTRACT_ACCOUNT_ID } from "@/common/_config"; @@ -14,7 +14,6 @@ import { donationFeePercentsToBasisPoints, } from "@/features/donation"; -import { POT_EDITOR_EXCLUDED_INDEXED_PROPERTIES } from "../constants"; import { PotSettings } from "../model"; import { PotConfigurationParameter, PotConfigurationParameterKey } from "../types"; @@ -39,76 +38,30 @@ export const potConfigToSettings = ({ }, ); -export const potIndexedDataToPotInputs = ({ - owner, - admins, - chef, - name, - description, - max_approved_applicants, - application_start, - application_end, - matching_round_start, - matching_round_end, +export const potConfigToPotConfigInputs = ({ registry_provider, sybil_wrapper_provider, - ...indexedPotData -}: Pot) => { - const potData = evolve(indexedPotData, { - referral_fee_matching_pool_basis_points: donationFeeBasisPointsToPercents, - referral_fee_public_round_basis_points: donationFeeBasisPointsToPercents, - chef_fee_basis_points: donationFeeBasisPointsToPercents, - - min_matching_pool_donation_amount: conditional( - [isNonNullish, yoctoNearToFloat], - conditional.defaultCase(() => undefined), - ), - }); - - return omit( - { - ...potData, - owner: owner.id, - admins: admins.map(prop("id")), - chef: chef?.id, - pot_name: name, - pot_description: description, - max_projects: max_approved_applicants, - - application_start_ms: Temporal.Instant.from(application_start) - .toZonedDateTimeISO(Temporal.Now.timeZoneId()) - .toPlainDateTime() - .toString(), - - application_end_ms: Temporal.Instant.from(application_end) - .toZonedDateTimeISO(Temporal.Now.timeZoneId()) - .toPlainDateTime() - .toString(), - - public_round_start_ms: Temporal.Instant.from(matching_round_start) - .toZonedDateTimeISO(Temporal.Now.timeZoneId()) - .toPlainDateTime() - .toString(), - - public_round_end_ms: Temporal.Instant.from(matching_round_end) - .toZonedDateTimeISO(Temporal.Now.timeZoneId()) - .toPlainDateTime() - .toString(), - - registry_provider: registry_provider ?? undefined, - isPgRegistrationRequired: typeof registry_provider === "string", - sybil_wrapper_provider: sybil_wrapper_provider ?? undefined, - isSybilResistanceEnabled: typeof sybil_wrapper_provider === "string", - }, - - { - ...POT_EDITOR_EXCLUDED_INDEXED_PROPERTIES, + owner, + ...potConfig +}: PotConfig): Omit => { + return { + ...evolve(potConfig, { + referral_fee_matching_pool_basis_points: donationFeeBasisPointsToPercents, + referral_fee_public_round_basis_points: donationFeeBasisPointsToPercents, + chef_fee_basis_points: donationFeeBasisPointsToPercents, - ...(potData.min_matching_pool_donation_amount === undefined - ? ["min_matching_pool_donation_amount"] - : []), - }, - ); + min_matching_pool_donation_amount: conditional( + [isNonNullish, yoctoNearToFloat], + conditional.defaultCase(() => 0.01), + ), + }), + + owner: owner ?? undefined, + registry_provider: registry_provider ?? undefined, + isPgRegistrationRequired: typeof registry_provider === "string", + sybil_wrapper_provider: sybil_wrapper_provider ?? undefined, + isSybilResistanceEnabled: typeof sybil_wrapper_provider === "string", + }; }; export const potInputsToPotArgs = ({ diff --git a/src/layout/pot/components/layout.tsx b/src/layout/pot/components/layout.tsx index dfa3e59f..7d6e1b91 100644 --- a/src/layout/pot/components/layout.tsx +++ b/src/layout/pot/components/layout.tsx @@ -122,7 +122,7 @@ export const PotLayout: React.FC = ({ children }) => {
{/* Tab Content */} -
{children}
+
{children}
); }; From 13825c0fbfa5d80de63ac485460f58c68b391cf9 Mon Sep 17 00:00:00 2001 From: Ebube111 Date: Mon, 3 Feb 2025 13:14:49 +0100 Subject: [PATCH 78/84] Done with Lists fixes for mainnet (#338) * done with listing fixes for prod * added type for listRegistrations * commited suggestions * Update src/entities/list/components/ListConfirmationModals.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix build error --------- Co-authored-by: Carina.Akaia.io Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/entities/list/components/AccountCard.tsx | 3 +- src/entities/list/components/ListAccounts.tsx | 74 ++++++++----------- .../components/ListConfirmationModals.tsx | 27 +++---- src/entities/list/components/ListDetails.tsx | 12 +-- .../list/components/ListFormDetails.tsx | 44 ++++++----- .../list/components/ListsOverview.tsx | 11 +-- .../list/components/listActionsModal.tsx | 6 +- src/pages/list/[id]/index.tsx | 22 +++--- src/pages/list/create/index.tsx | 3 +- src/pages/list/duplicate/[id].tsx | 4 +- src/pages/list/edit/[id]/index.tsx | 4 +- src/pages/lists.tsx | 1 - 12 files changed, 100 insertions(+), 111 deletions(-) diff --git a/src/entities/list/components/AccountCard.tsx b/src/entities/list/components/AccountCard.tsx index 985b5508..7875cfc3 100644 --- a/src/entities/list/components/AccountCard.tsx +++ b/src/entities/list/components/AccountCard.tsx @@ -42,8 +42,7 @@ interface StatusModal { status?: RegistrationStatus | string; } -// TODO: Rename to `ListAccountCard`! -export const AccountCard = ({ +export const ListAccountCard = ({ dataForList, accountsWithAccess, }: { diff --git a/src/entities/list/components/ListAccounts.tsx b/src/entities/list/components/ListAccounts.tsx index c47cd128..9e1211b2 100644 --- a/src/entities/list/components/ListAccounts.tsx +++ b/src/entities/list/components/ListAccounts.tsx @@ -1,21 +1,20 @@ /* eslint-disable prettier/prettier */ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { List } from "@/common/api/indexer"; +import { List, ListRegistration } from "@/common/api/indexer"; import { Filter, Group, GroupType, SearchBar, SortSelect } from "@/common/ui/components"; import { ACCOUNT_LIST_REGISTRATION_STATUS_OPTIONS } from "@/entities/_shared"; -import { AccountCard } from "./AccountCard"; import { ListCardSkeleton } from "./ListCardSkeleton"; import { NoListItem } from "./NoListItem"; import { NoListItemType } from "../types"; +import { ListAccountCard } from "./AccountCard"; interface ListAccountsType { loadingListData: boolean; listData: List | undefined; - filteredRegistrations: any[]; + listRegistrations: ListRegistration[]; isLoading: boolean; - setFilteredRegistrations: (value: any) => void; setStatus: (value: string) => void; } @@ -24,15 +23,14 @@ export const ListAccounts = ({ loadingListData, setStatus, isLoading, - filteredRegistrations, - setFilteredRegistrations, + listRegistrations, }: ListAccountsType) => { const [search, setSearch] = useState(""); const [accountsWithAccess, setAccountsWithAccess] = useState([]); const [statusFilter, setsStatusFilter] = useState("all"); - const [searchedAccounts, setSearchedAccounts] = useState([]); - const SORT_LIST_PROJEECTS = [ + + const SORT_LIST_PROJECTS = [ { label: "Most recent", value: "recent" }, { label: "Least recent", value: "older" }, ]; @@ -52,7 +50,7 @@ export const ListAccounts = ({ }, ]; - const handleFilter = (registration: any) => { + const handleFilter = (registration: ListRegistration) => { const matchesSearch = search ? registration.registrant?.near_social_profile_data?.name ?.toLowerCase() @@ -63,37 +61,23 @@ export const ListAccounts = ({ return matchesSearch; }; - useEffect(() => { - const filtered = filteredRegistrations.filter(handleFilter); - setSearchedAccounts(filtered ?? []); - }, [search]); - - const handleSort = (sortType: string) => { - const projects = [...filteredRegistrations]; - - switch (sortType) { - case "recent": - projects.sort( - (a, b) => - new Date(b.submitted_at).getTime() - - new Date(a.submitted_at).getTime(), - ); - - setFilteredRegistrations(projects); - break; - case "older": - projects.sort( - (a, b) => - new Date(a.submitted_at).getTime() - - new Date(b.submitted_at).getTime(), - ); - - setFilteredRegistrations(projects); - break; - default: - break; - } - }; + + const searchedAccounts = useMemo(() => { + return listRegistrations.filter(handleFilter); + }, [search, handleFilter]) + + const handleSort = useCallback((sortType: string) => { + return [...listRegistrations].sort((a, b) => { + switch (sortType) { + case "recent": + return new Date(b.submitted_at).getTime() - new Date(a.submitted_at).getTime(); + case "older": + return new Date(a.submitted_at).getTime() - new Date(b.submitted_at).getTime(); + default: + return 0; // No sorting + } + }); + }, [listRegistrations]); useEffect(() => { if (!loadingListData && listData) { @@ -106,7 +90,7 @@ export const ListAccounts = ({ } }, [listData]); - const data = search ? searchedAccounts : filteredRegistrations ?? []; + const data = search ? searchedAccounts : listRegistrations ?? []; return (
@@ -117,7 +101,7 @@ export const ListAccounts = ({ - {filteredRegistrations?.length} + {listRegistrations?.length}
@@ -128,7 +112,7 @@ export const ListAccounts = ({ />
@@ -142,7 +126,7 @@ export const ListAccounts = ({ ) : data?.length ? (
{data?.map((item, index) => ( - void; listName: string; - onViewList: () => void; isUpdate: boolean; + href: string; showBackToLists?: boolean; } @@ -20,7 +21,7 @@ export const SuccessModalCreateList: React.FC = ({ listName, isUpdate, showBackToLists, - onViewList, + href, }) => { if (!isOpen) return null; @@ -46,17 +47,17 @@ export const SuccessModalCreateList: React.FC = ({

You’ve successfully {isUpdate ? "Updated" : "Deployed"} {listName}, you can always make - adjustments in the pot settings page. + adjustments in the list settings page.

- +
diff --git a/src/entities/list/components/ListDetails.tsx b/src/entities/list/components/ListDetails.tsx index ae534423..b9051a62 100644 --- a/src/entities/list/components/ListDetails.tsx +++ b/src/entities/list/components/ListDetails.tsx @@ -40,16 +40,12 @@ interface ListDetailsType { setAdmins: (value: string[]) => void; listDetails: List | any; savedUsers: SavedUsersType; + listId: number; } -export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType) => { +export const ListDetails = ({ admins, listId, listDetails, savedUsers }: ListDetailsType) => { const viewer = useWalletUserSession(); - - const { - push, - // TODO: Pass this values down from the page level! - query: { id }, - } = useRouter(); + const { push } = useRouter(); const [isApplyToListModalOpen, setIsApplyToListModalOpen] = useState(false); const [isApplicationSuccessful, setIsApplicationSuccessful] = useState(false); @@ -219,7 +215,7 @@ export const ListDetails = ({ admins, listDetails, savedUsers }: ListDetailsType {viewer.isSignedIn && (
- + {!listDetails?.admin_only_registrations && ( )} - {status !== "Rejected" && ( + {status !== "Rejected" && status !== "Approved" && (
diff --git a/src/features/pot-application/hooks/forms.ts b/src/features/pot-application/hooks/forms.ts index f67a929c..2fe22474 100644 --- a/src/features/pot-application/hooks/forms.ts +++ b/src/features/pot-application/hooks/forms.ts @@ -8,6 +8,7 @@ import { FormSubmitHandler, useForm } from "react-hook-form"; import { Pot } from "@/common/api/indexer"; import { naxiosInstance } from "@/common/blockchains/near-protocol/client"; import { FULL_TGAS, MIN_PROPOSAL_DEPOSIT_FALLBACK, ONE_TGAS } from "@/common/constants"; +import { potContractClient } from "@/common/contracts/core/pot"; import { getDaoPolicy } from "@/common/contracts/sputnik-dao"; import { @@ -114,15 +115,19 @@ export const usePotApplicationForm = ({ }; }; +export type PotApplicationReviewParams = { + potDetail: Pot; + projectId: string; + status: "Approved" | "Rejected" | ""; + onSuccess: () => void; +}; + export const usePotApplicationReviewForm = ({ potDetail, projectId, status, -}: { - potDetail: Pot; - projectId: string; - status: "Approved" | "Rejected" | ""; -}) => { + onSuccess, +}: PotApplicationReviewParams) => { const form = useForm({ resolver: zodResolver(potApplicationReviewSchema), }); @@ -130,7 +135,7 @@ export const usePotApplicationReviewForm = ({ const [inProgress, setInProgress] = useState(false); const onSubmit: FormSubmitHandler = useCallback( - async (formData) => { + (formData) => { setInProgress(true); const args = { @@ -139,20 +144,23 @@ export const usePotApplicationReviewForm = ({ notes: formData.data.message, }; - try { - await naxiosInstance - .contractApi({ contractId: potDetail.account }) - .call("chef_set_application_status", { - args, - deposit: parseNearAmount(calculateDepositByDataSize(args))!, - gas: FULL_TGAS, - }); - } catch (e) { - console.error(e); - setInProgress(false); - } + potContractClient + .chef_set_application_status({ + ...args, + potId: potDetail.account, + }) + .then(() => { + onSuccess(); + }) + .catch((error) => { + console.error(error); + }) + .finally(() => { + setInProgress(false); + }); }, - [potDetail.account, projectId, status], + + [onSuccess, potDetail.account, projectId, status], ); return { diff --git a/src/features/proportional-funding/components/payout-manager.tsx b/src/features/proportional-funding/components/payout-manager.tsx index 9d05d136..dbd2ba18 100644 --- a/src/features/proportional-funding/components/payout-manager.tsx +++ b/src/features/proportional-funding/components/payout-manager.tsx @@ -23,7 +23,7 @@ export const ProportionalFundingPayoutManager: React.FC { const { toast } = useToast(); const viewer = useWalletUserSession(); - const authorizedUser = usePotAuthorization({ potId, accountId: viewer.accountId }); + const viewerAbilities = usePotAuthorization({ potId, accountId: viewer.accountId }); const votingRoundResults = useVotingRoundResults({ potId }); const { isMetadataLoading: isTokenMetadataLoading, data: token } = useToken({ @@ -97,7 +97,7 @@ export const ProportionalFundingPayoutManager: React.FC )} - {authorizedUser.canSubmitPayouts && ( + {viewerAbilities.canSubmitPayouts && ( <> {votingRoundResults.isLoading || isTokenMetadataLoading ? ( @@ -110,7 +110,7 @@ export const ProportionalFundingPayoutManager: React.FC )} - {authorizedUser.canInitiatePayoutProcessing && ( + {viewerAbilities.canInitiatePayoutProcessing && (
- {authorizedUser.canApply && applicationClearance.isEveryRequirementSatisfied && ( + {viewerAbilities.canApply && applicationClearance.isEveryRequirementSatisfied && ( )} {hasProportionalFundingMechanism ? null : ( - <>{authorizedUser.canDonate && } + <>{viewerAbilities.canDonate && } )} - {authorizedUser.canFundMatchingPool && ( + {viewerAbilities.canFundMatchingPool && ( )} - {authorizedUser.canChallengePayouts && ( + {viewerAbilities.canChallengePayouts && ( diff --git a/src/pages/pot/[potId]/applications.tsx b/src/pages/pot/[potId]/applications.tsx index ad77244f..cbda4566 100644 --- a/src/pages/pot/[potId]/applications.tsx +++ b/src/pages/pot/[potId]/applications.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import Image from "next/image"; import { useRouter } from "next/router"; @@ -88,7 +88,7 @@ export default function ApplicationsTab() { const handleApproveApplication = (accountId: AccountId) => { setSelectedApplicantAccountId(accountId); - setStatusFilter("Approved"); + setProjectStatus("Approved"); }; const handleRejectApplication = (accountId: AccountId) => { @@ -101,6 +101,11 @@ export default function ApplicationsTab() { setProjectStatus(""); }; + const onReviewSuccess = useCallback(() => { + refetchApplications(); + handleCloseModal(); + }, [refetchApplications]); + const applicationsFilters: Record< AccountPotApplicationStatusVariant, AccountPotApplicationStatusOption & { count?: number } @@ -141,9 +146,6 @@ export default function ApplicationsTab() { }; }, [applications]); - const isChefOrGreater = - accountId === chef || admins.includes(accountId || "") || accountId === owner; - useEffect(() => { if (error) { console.log(error); @@ -153,7 +155,7 @@ export default function ApplicationsTab() { }, [statusFilter, error]); return ( - + {/* Modal */} {potDetail && ( )} @@ -192,9 +195,9 @@ export default function ApplicationsTab() { )) ) : ( From 1db7daadfe4750df060b0d9c0221ba40ec56f8f8 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 3 Feb 2025 12:42:17 +0000 Subject: [PATCH 80/84] Fix pot application flow issues --- src/entities/pot/hooks/authorization.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/entities/pot/hooks/authorization.ts b/src/entities/pot/hooks/authorization.ts index 9e28d628..c7325a7e 100644 --- a/src/entities/pot/hooks/authorization.ts +++ b/src/entities/pot/hooks/authorization.ts @@ -2,7 +2,7 @@ import { useMemo } from "react"; import { ByPotId } from "@/common/api/indexer"; import { IS_UNDER_INSPECTION } from "@/common/constants"; -import { potContractHooks } from "@/common/contracts/core/pot"; +import { ApplicationStatus, potContractHooks } from "@/common/contracts/core/pot"; import { isAccountId } from "@/common/lib"; import type { ByAccountId } from "@/common/types"; @@ -82,8 +82,11 @@ export const usePotAuthorization = ({ potId, accountId }: ByPotId & Partial isValidAccountId && isPublicRoundOngoing, - [isValidAccountId, isPublicRoundOngoing], + () => + isValidAccountId && + isPublicRoundOngoing && + potApplications?.find(({ status }) => status === ApplicationStatus.Approved) !== undefined, + [isValidAccountId, isPublicRoundOngoing, potApplications], ); const canApply = useMemo( From a28b91972dd22a365f2f06762a377055437c443d Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 3 Feb 2025 12:54:05 +0000 Subject: [PATCH 81/84] Fix pot application flow issues --- .../components/PotDeploymentModal.tsx | 47 ----------- .../components/PotDeploymentSuccess.tsx | 38 --------- ...{PotConfigurationEditor.tsx => editor.tsx} | 2 +- .../{PotDeploymentError.tsx => error.tsx} | 0 ...otConfigurationPreview.tsx => preview.tsx} | 0 .../components/success-modal.tsx | 82 +++++++++++++++++++ .../pot-configuration/hooks/middlewares.ts | 2 +- src/features/pot-configuration/index.ts | 2 +- 8 files changed, 85 insertions(+), 88 deletions(-) delete mode 100644 src/features/pot-configuration/components/PotDeploymentModal.tsx delete mode 100644 src/features/pot-configuration/components/PotDeploymentSuccess.tsx rename src/features/pot-configuration/components/{PotConfigurationEditor.tsx => editor.tsx} (99%) rename src/features/pot-configuration/components/{PotDeploymentError.tsx => error.tsx} (100%) rename src/features/pot-configuration/components/{PotConfigurationPreview.tsx => preview.tsx} (100%) create mode 100644 src/features/pot-configuration/components/success-modal.tsx diff --git a/src/features/pot-configuration/components/PotDeploymentModal.tsx b/src/features/pot-configuration/components/PotDeploymentModal.tsx deleted file mode 100644 index ba22e95d..00000000 --- a/src/features/pot-configuration/components/PotDeploymentModal.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useCallback, useMemo } from "react"; - -import { create, useModal } from "@ebay/nice-modal-react"; - -import { DataLoadingPlaceholder, Dialog, DialogContent } from "@/common/ui/components"; - -import { PotDeploymentError } from "./PotDeploymentError"; -import { PotDeploymentSuccess } from "./PotDeploymentSuccess"; -import { usePotConfigurationState } from "../model"; - -export type PotDeploymentModalProps = {}; - -export const PotDeploymentModal = create((_: PotDeploymentModalProps) => { - const self = useModal(); - - const close = useCallback(() => { - self.hide(); - self.remove(); - }, [self]); - - const { - finalOutcome: { data, error }, - } = usePotConfigurationState(); - - const content = useMemo( - () => - error !== null || !data ? ( - - ) : ( - - ), - - [close, data, error], - ); - - return ( - - - {data === undefined ? ( - - ) : ( - content - )} - - - ); -}); diff --git a/src/features/pot-configuration/components/PotDeploymentSuccess.tsx b/src/features/pot-configuration/components/PotDeploymentSuccess.tsx deleted file mode 100644 index f00f2142..00000000 --- a/src/features/pot-configuration/components/PotDeploymentSuccess.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import Link from "next/link"; - -import { Button, DialogDescription } from "@/common/ui/components"; -import { ChefHatIcon } from "@/common/ui/svg"; -import { PotData } from "@/entities/pot"; -import { rootPathnames } from "@/pathnames"; - -export type PotDeploymentSuccessProps = { - onViewPotClick: VoidFunction; - potData: PotData; -}; - -export const PotDeploymentSuccess: React.FC = ({ - onViewPotClick, - potData, -}) => ( - -
- -
- -
-

- {"Successfully deployed!"} -

- -

- {"You've successfully deployed " + - potData.pot_name + - ", you can always make adjustments in the pot settings page."} -

-
- - -
-); diff --git a/src/features/pot-configuration/components/PotConfigurationEditor.tsx b/src/features/pot-configuration/components/editor.tsx similarity index 99% rename from src/features/pot-configuration/components/PotConfigurationEditor.tsx rename to src/features/pot-configuration/components/editor.tsx index 364b97d0..8f604ace 100644 --- a/src/features/pot-configuration/components/PotConfigurationEditor.tsx +++ b/src/features/pot-configuration/components/editor.tsx @@ -27,7 +27,7 @@ import { AccountGroup } from "@/entities/_shared/account"; import { POT_MAX_DESCRIPTION_LENGTH } from "@/entities/pot"; import { DONATION_MIN_NEAR_AMOUNT } from "@/features/donation"; -import { PotConfigurationPreview } from "./PotConfigurationPreview"; +import { PotConfigurationPreview } from "./preview"; import { POT_EDITOR_FIELDS } from "../constants"; import { usePotConfigurationEditorForm } from "../hooks/forms"; import { getPotDeploymentSchema, getPotSettingsSchema } from "../model"; diff --git a/src/features/pot-configuration/components/PotDeploymentError.tsx b/src/features/pot-configuration/components/error.tsx similarity index 100% rename from src/features/pot-configuration/components/PotDeploymentError.tsx rename to src/features/pot-configuration/components/error.tsx diff --git a/src/features/pot-configuration/components/PotConfigurationPreview.tsx b/src/features/pot-configuration/components/preview.tsx similarity index 100% rename from src/features/pot-configuration/components/PotConfigurationPreview.tsx rename to src/features/pot-configuration/components/preview.tsx diff --git a/src/features/pot-configuration/components/success-modal.tsx b/src/features/pot-configuration/components/success-modal.tsx new file mode 100644 index 00000000..e809dd6a --- /dev/null +++ b/src/features/pot-configuration/components/success-modal.tsx @@ -0,0 +1,82 @@ +import { useCallback, useMemo } from "react"; + +import { create, useModal } from "@ebay/nice-modal-react"; +import Link from "next/link"; + +import { + Button, + DataLoadingPlaceholder, + Dialog, + DialogContent, + DialogDescription, +} from "@/common/ui/components"; +import { ChefHatIcon } from "@/common/ui/svg"; +import { rootPathnames } from "@/pathnames"; + +import { PotDeploymentError } from "./error"; +import { usePotConfigurationState } from "../model"; + +export type PotDeploymentModalProps = {}; + +/** + * @deprecated use toasts instead. + */ +export const PotDeploymentModal = create((_: PotDeploymentModalProps) => { + const self = useModal(); + + const close = useCallback(() => { + self.hide(); + self.remove(); + }, [self]); + + const { + finalOutcome: { data, error }, + } = usePotConfigurationState(); + + const content = useMemo( + () => + error !== null ? ( + + ) : ( + <> + {data === undefined || data === null ? null : ( + +
+ +
+ +
+

+ {"Successfully deployed!"} +

+ +

+ {"You've successfully deployed " + + data.pot_name + + ", you can always make adjustments in the pot settings page."} +

+
+ + +
+ )} + + ), + + [data, error], + ); + + return ( + + + {data === undefined ? ( + + ) : ( + content + )} + + + ); +}); diff --git a/src/features/pot-configuration/hooks/middlewares.ts b/src/features/pot-configuration/hooks/middlewares.ts index 0b7e55de..de39fb98 100644 --- a/src/features/pot-configuration/hooks/middlewares.ts +++ b/src/features/pot-configuration/hooks/middlewares.ts @@ -5,7 +5,7 @@ import { useModal } from "@ebay/nice-modal-react"; import { useRouteQuery } from "@/common/lib"; import { dispatch } from "@/store"; -import { PotDeploymentModal } from "../components/PotDeploymentModal"; +import { PotDeploymentModal } from "../components/success-modal"; export const usePotDeploymentSuccessMiddleware = () => { const resultModal = useModal(PotDeploymentModal); diff --git a/src/features/pot-configuration/index.ts b/src/features/pot-configuration/index.ts index 060f4c99..04f103a9 100644 --- a/src/features/pot-configuration/index.ts +++ b/src/features/pot-configuration/index.ts @@ -1,3 +1,3 @@ export * from "./hooks/middlewares"; -export * from "./components/PotConfigurationEditor"; +export * from "./components/editor"; export { potConfigurationModel, potConfigurationModelKey } from "./model"; From b5ffb8f17dc7b180e14ba94baf00c4edbeb52d28 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 3 Feb 2025 14:19:24 +0000 Subject: [PATCH 82/84] Temporarily freeze profile setup on production --- .vscode/settings.json | 1 + src/common/_config/production.env-config.ts | 6 ++ src/common/_config/staging.env-config.ts | 6 ++ src/common/_config/test.env-config.ts | 6 ++ src/common/constants.ts | 1 + src/common/types.ts | 5 ++ .../ui/components/_deprecated/InfoSegment.tsx | 59 ------------------- .../pot-configuration/components/editor.tsx | 5 +- src/layout/profile/components/summary.tsx | 25 +++++--- src/pages/profile/[accountId]/edit.tsx | 59 +++++++++++++------ src/pages/register.tsx | 31 +++++++--- tsconfig.json | 2 +- 12 files changed, 110 insertions(+), 96 deletions(-) delete mode 100644 src/common/ui/components/_deprecated/InfoSegment.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 8884580c..ecfe7872 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,6 +60,7 @@ "defi", "deployers", "dontcare", + "editprofile", "Elrond", "extralight", "fastnear", diff --git a/src/common/_config/production.env-config.ts b/src/common/_config/production.env-config.ts index 6151a992..9365198d 100644 --- a/src/common/_config/production.env-config.ts +++ b/src/common/_config/production.env-config.ts @@ -62,6 +62,12 @@ export const envConfig: EnvConfig = { }, features: { + [FeatureId.ProfileConfiguration]: { + id: FeatureId.ProfileConfiguration, + name: "Profile configuration", + isEnabled: false, + }, + [FeatureId.DirectFtDonation]: { id: FeatureId.DirectFtDonation, name: "Direct FT donation", diff --git a/src/common/_config/staging.env-config.ts b/src/common/_config/staging.env-config.ts index 5ea05987..4071c107 100644 --- a/src/common/_config/staging.env-config.ts +++ b/src/common/_config/staging.env-config.ts @@ -62,6 +62,12 @@ export const envConfig: EnvConfig = { }, features: { + [FeatureId.ProfileConfiguration]: { + id: FeatureId.ProfileConfiguration, + name: "Profile configuration", + isEnabled: true, + }, + [FeatureId.DirectFtDonation]: { id: FeatureId.DirectFtDonation, name: "Direct FT donation", diff --git a/src/common/_config/test.env-config.ts b/src/common/_config/test.env-config.ts index b2c161c6..a576a890 100644 --- a/src/common/_config/test.env-config.ts +++ b/src/common/_config/test.env-config.ts @@ -60,6 +60,12 @@ export const envConfig: EnvConfig = { }, features: { + [FeatureId.ProfileConfiguration]: { + id: FeatureId.ProfileConfiguration, + name: "Profile configuration", + isEnabled: true, + }, + [FeatureId.DirectFtDonation]: { id: FeatureId.DirectFtDonation, name: "Direct FT donation", diff --git a/src/common/constants.ts b/src/common/constants.ts index 43afc14a..f5a37e58 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -20,6 +20,7 @@ export const ICONS_ASSET_ENDPOINT_URL = "/assets/icons"; export const IMAGES_ASSET_ENDPOINT_URL = "/assets/images"; export const PLATFORM_TWITTER_ACCOUNT_ID = "PotLock_"; export const DEFAULT_SHARE_HASHTAGS = ["PublicGoods", "Donations"]; +export const APP_BOS_COUNTERPART_URL = "https://bos.potlock.org"; export const APP_METADATA: Metadata & { title: string; diff --git a/src/common/types.ts b/src/common/types.ts index c2437589..54ab51fa 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -16,6 +16,11 @@ export enum FeatureId { * Donation to a single account using blockchain's native token. */ DirectNativeTokenDonation = "DirectNativeTokenDonation", + + /** + * As a User, I want to be able to register on the platform and update my profile details + */ + ProfileConfiguration = "ProfileConfiguration", } export type FeatureFlags = { isEnabled: boolean }; diff --git a/src/common/ui/components/_deprecated/InfoSegment.tsx b/src/common/ui/components/_deprecated/InfoSegment.tsx deleted file mode 100644 index 4bef3d20..00000000 --- a/src/common/ui/components/_deprecated/InfoSegment.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { MdInfo } from "react-icons/md"; -import { styled } from "styled-components"; - -export const Container = styled.div` - box-sizing: border-box; - display: flex; - flex-direction: row; - align-items: flex-start; - padding: 1em; - gap: 0.75em; - background: #fcfcfd; - border: 1px solid #d0d5dd; - border-radius: 4px; - width: 100%; - margin-top: 2rem; - svg { - width: 20px; - } -`; - -export const Text = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 0px; - gap: 0.75em; -`; - -export const Heading = styled.div` - font-style: normal; - font-weight: 600; - font-size: 0.95em; - line-height: 1.25em; - color: #344054; -`; - -export const Description = styled.p` - font-style: normal; - font-weight: 400; - font-size: 0.95em; - line-height: 1.25em; - color: #475467; - white-space: wrap; - margin: 0px; -`; - -const InfoSegment = ({ title, description }: { title: string; description: string }) => { - return ( - - - - {title} - {description} - - - ); -}; - -export default InfoSegment; diff --git a/src/features/pot-configuration/components/editor.tsx b/src/features/pot-configuration/components/editor.tsx index 8f604ace..22db489b 100644 --- a/src/features/pot-configuration/components/editor.tsx +++ b/src/features/pot-configuration/components/editor.tsx @@ -13,6 +13,7 @@ import { EditorSection, Form, FormField, + SplashScreen, } from "@/common/ui/components"; import { CheckboxField, @@ -75,7 +76,9 @@ export const PotConfigurationEditor: React.FC = ({ ) : ( <> - {isHydrating ? null : ( + {isHydrating ? ( + + ) : (

diff --git a/src/layout/profile/components/summary.tsx b/src/layout/profile/components/summary.tsx index 0f3b9d61..c78dea20 100644 --- a/src/layout/profile/components/summary.tsx +++ b/src/layout/profile/components/summary.tsx @@ -4,8 +4,9 @@ import Link from "next/link"; import { CopyToClipboard } from "react-copy-to-clipboard"; import { styled } from "styled-components"; +import { FEATURE_REGISTRY } from "@/common/_config"; import { indexer } from "@/common/api/indexer"; -import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; +import { APP_BOS_COUNTERPART_URL, PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { listsContractHooks } from "@/common/contracts/core/lists"; import { truncate } from "@/common/lib"; import type { ByAccountId } from "@/common/types"; @@ -55,8 +56,9 @@ const Linktree: React.FC = ({ accountId }) => { pathClassName="group-hover:fill-[#292929] transition-all ease-in-out" /> )} +

- Earn referral fees + {"Earn referral fees"}

@@ -167,11 +169,20 @@ export const ProfileLayoutSummary: React.FC = ({ acco {isOwner && (
- - - +
)}
diff --git a/src/pages/profile/[accountId]/edit.tsx b/src/pages/profile/[accountId]/edit.tsx index b70491ad..3fbe8098 100644 --- a/src/pages/profile/[accountId]/edit.tsx +++ b/src/pages/profile/[accountId]/edit.tsx @@ -1,19 +1,11 @@ import { useCallback, useEffect, useMemo } from "react"; -import Link from "next/link"; import { useRouter } from "next/router"; -import { MdOutlineInfo } from "react-icons/md"; +import { MdOutlineHourglassTop, MdOutlineInfo } from "react-icons/md"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; -import { - Alert, - AlertDescription, - AlertTitle, - Button, - PageWithBanner, -} from "@/common/ui/components"; -import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; +import { Alert, AlertDescription, AlertTitle, PageWithBanner } from "@/common/ui/components"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; import { useWalletUserSession } from "@/common/wallet"; @@ -22,13 +14,18 @@ import { rootPathnames } from "@/pathnames"; export default function EditProjectPage() { const router = useRouter(); - // const { accountId } = router.query as { accountId: AccountId }; + const { accountId } = router.query as { accountId: string }; const viewer = useWalletUserSession(); const { toast } = useToast(); + const isOwner = useMemo( + () => viewer.hasWalletReady && viewer.isSignedIn && accountId === viewer.accountId, + [accountId, viewer.accountId, viewer.hasWalletReady, viewer.isSignedIn], + ); + const { isLoading: isAccountListRegistrationDataLoading, data: listRegistrations } = indexer.useAccountListRegistrations({ - enabled: viewer.isSignedIn, + enabled: viewer.hasWalletReady && isOwner, accountId: viewer.accountId ?? "noop", }); @@ -60,11 +57,21 @@ export default function EditProjectPage() { if (viewer.isSignedIn && !isAccountListRegistrationDataLoading && !hasRegistrationSubmitted) { router.push(rootPathnames.REGISTER); } + + if (viewer.hasWalletReady && viewer.isSignedIn && !isOwner) { + toast({ variant: "destructive", title: `You are not the owner of ${accountId}.` }); + router.push(`${rootPathnames.PROFILE}/${accountId}`); + } }, [ + accountId, hasRegistrationSubmitted, isAccountListRegistrationDataLoading, + isOwner, listRegistrations, router, + toast, + viewer.accountId, + viewer.hasWalletReady, viewer.isSignedIn, ]); @@ -85,10 +92,14 @@ export default function EditProjectPage() { - {viewer.isSignedIn ? ( + {viewer.hasWalletReady && viewer.isSignedIn ? ( <> {listRegistrations === undefined ? ( - + + + {"Checking Account"} + {"Please, wait..."} + ) : ( ) : ( - - - {"Not logged in"} - {"Please connect your wallet to continue"} - + <> + {viewer.hasWalletReady ? ( + + + {"Not Signed In"} + {"Please connect your wallet to continue"} + + ) : ( + + + {"Checking Account"} + {"Please, wait..."} + + )} + )} ); diff --git a/src/pages/register.tsx b/src/pages/register.tsx index d8c85769..544537a2 100644 --- a/src/pages/register.tsx +++ b/src/pages/register.tsx @@ -1,12 +1,11 @@ import { useCallback, useEffect, useMemo } from "react"; import { useRouter } from "next/router"; -import { MdOutlineInfo } from "react-icons/md"; +import { MdOutlineHourglassTop, MdOutlineInfo } from "react-icons/md"; import { indexer } from "@/common/api/indexer"; import { PUBLIC_GOODS_REGISTRY_LIST_ID } from "@/common/constants"; import { Alert, AlertDescription, AlertTitle, PageWithBanner } from "@/common/ui/components"; -import InfoSegment from "@/common/ui/components/_deprecated/InfoSegment"; import { useToast } from "@/common/ui/hooks"; import { cn } from "@/common/ui/utils"; import { useWalletUserSession } from "@/common/wallet"; @@ -77,10 +76,14 @@ export default function RegisterPage() { - {viewer.isSignedIn ? ( + {viewer.hasWalletReady && viewer.isSignedIn ? ( <> {listRegistrations === undefined ? ( - + + + {"Checking Account"} + {"Please, wait..."} + ) : ( ) : ( - - - {"Not logged in"} - {"Please connect your wallet to continue"} - + <> + {viewer.hasWalletReady ? ( + + + {"Not Signed In"} + {"Please connect your wallet to continue"} + + ) : ( + + + {"Checking Account"} + {"Please, wait..."} + + )} + )} ); diff --git a/tsconfig.json b/tsconfig.json index bf8b1ece..d20a6003 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "dom.iterable", "esnext" ], - "target": "es2020", + "target": "es2022", "allowJs": true, "skipLibCheck": true, "strict": true, From a09eb08a4cfdcfead985747ccaad4a556f641abb Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 3 Feb 2025 14:20:34 +0000 Subject: [PATCH 83/84] chore: Update CSpell dictionary --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index ecfe7872..8884580c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -60,7 +60,6 @@ "defi", "deployers", "dontcare", - "editprofile", "Elrond", "extralight", "fastnear", From b2345890fe4fad172435fdbaef1a428bfc0a4ff8 Mon Sep 17 00:00:00 2001 From: Carina Akaia Date: Mon, 3 Feb 2025 14:56:48 +0000 Subject: [PATCH 84/84] Set production feature flag to enabled --- src/common/_config/production.env-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/_config/production.env-config.ts b/src/common/_config/production.env-config.ts index 9365198d..602a97e0 100644 --- a/src/common/_config/production.env-config.ts +++ b/src/common/_config/production.env-config.ts @@ -65,7 +65,7 @@ export const envConfig: EnvConfig = { [FeatureId.ProfileConfiguration]: { id: FeatureId.ProfileConfiguration, name: "Profile configuration", - isEnabled: false, + isEnabled: true, }, [FeatureId.DirectFtDonation]: {