Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
carina-akaia committed Jan 22, 2025
1 parent 9a5a5fa commit e715b2e
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 88 deletions.
38 changes: 15 additions & 23 deletions src/features/profile-setup/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,19 @@ export const ProfileSetupForm: React.FC<ProfileSetupFormProps> = ({ 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,
Expand All @@ -90,7 +100,8 @@ export const ProfileSetupForm: React.FC<ProfileSetupFormProps> = ({ accountId, m
} = useProfileSetupForm({
accountId,
defaultValues,
onSuccess: () => router.push(rootPathnames.PROJECTS_LIST),
onSuccess,
onFailure,
mode,
});

Expand All @@ -99,9 +110,6 @@ export const ProfileSetupForm: React.FC<ProfileSetupFormProps> = ({ accountId, m
form.setFocus("name");
}, [form]);

const stringifiedDefaultValues = JSON.stringify(defaultValues);
const stringifiedValues = JSON.stringify(values);

const onCategoriesChange = useCallback(
(categories: string[]) => updateCategories(categories),
[updateCategories],
Expand All @@ -119,10 +127,6 @@ export const ProfileSetupForm: React.FC<ProfileSetupFormProps> = ({ 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";
Expand All @@ -146,14 +150,6 @@ export const ProfileSetupForm: React.FC<ProfileSetupFormProps> = ({ accountId, m

console.log(form.formState.errors, form.formState.isValid);

// console.log({ stringifiedDefaultValues, stringifiedValues });

console.log("isThereAChange?", isThereDiff, { isSubmitting });

if (form.formState.isLoading) {
return <InfoSegment title="Loading project data..." description="Please wait..." />;
}

return (
<Form {...form}>
<div className="m-auto flex w-full max-w-[816px] flex-col p-[3rem_0px] md:p-[4rem_0px]">
Expand Down Expand Up @@ -320,14 +316,10 @@ export const ProfileSetupForm: React.FC<ProfileSetupFormProps> = ({ accountId, m
router.push(rootPathnames.PROJECTS_LIST);
}}
>
Cancel
{"Cancel"}
</Button>
<Button
variant="standard-filled"
type="submit"
disabled={isSubmitting || !isThereDiff}
onClick={onSubmit}
>

<Button variant="standard-filled" type="submit" disabled={isDisabled} onClick={onSubmit}>
{projectEditorText}
</Button>
</div>
Expand Down
94 changes: 53 additions & 41 deletions src/features/profile-setup/hooks/forms.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
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";
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";
import { AddFundingSourceInputs, ProfileSetupInputs } from "../models/types";

export type ProfileSetupFormParams = ByAccountId &
Pick<ProfileSaveInputs, "mode"> & {
onSuccess?: () => void;
onSuccess: () => void;
onFailure: (errorMessage: string) => void;
defaultValues?: Partial<ProfileSetupInputs>;
};

export const useProfileSetupForm = ({
accountId,
mode,
onSuccess,
onFailure,
defaultValues,
}: ProfileSetupFormParams) => {
const [submitting, setSubmitting] = useState(false);

const [crossFieldErrors, setCrossFieldErrors] = useState<FieldErrors<ProfileSetupInputs>>({});
const { isSignedIn } = useSession();

const form = useForm<ProfileSetupInputs>({
const self = useForm<ProfileSetupInputs>({
resolver: zodResolver(profileSetupSchema),
mode: "onChange",
defaultValues,
Expand All @@ -40,14 +41,20 @@ 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(() => {
void profileSetupSchema
.parseAsync(values)
.then(() => setCrossFieldErrors({}))
.catch((error: ZodError) =>
// TODO: Consider using `setErrors`
setCrossFieldErrors(
error?.issues.reduce((errors, { code, message, path }) => {
const fieldPath = path.at(0);
Expand All @@ -61,86 +68,91 @@ 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<ProfileSetupInputs> = useCallback(async () => {
if (isSignedIn) {
setSubmitting(true);
self.setValue("githubRepositories", [...currentRepos, ""], { shouldValidate: true });
}, [self, values.githubRepositories]);

const onSubmit: SubmitHandler<ProfileSetupInputs> = 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,
updateProfileImage,
updateRepositories,
updateTeamMembers,
addRepository,
onSubmit: form.handleSubmit(onSubmit),
resetForm: form.reset,
onSubmit: self.handleSubmit(onSubmit),
resetForm: self.reset,
};
};

Expand Down
33 changes: 13 additions & 20 deletions src/features/profile-setup/models/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down Expand Up @@ -64,5 +64,3 @@ const getSocialDataFormat = (data: ProjectEditorState) => {

return body;
};

export default getSocialDataFormat;

0 comments on commit e715b2e

Please sign in to comment.