Skip to content

Commit

Permalink
fix: image upload only after click submit
Browse files Browse the repository at this point in the history
  • Loading branch information
kittybest committed Jul 22, 2024
1 parent 7122add commit adac895
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 49 deletions.
21 changes: 4 additions & 17 deletions src/components/ImageUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { Controller, useFormContext } from "react-hook-form";
import { toast } from "sonner";

import { IconButton } from "~/components/ui/Button";
import { Spinner } from "~/components/ui/Spinner";
import { useUploadMetadata } from "~/hooks/useMetadata";

export interface IImageUploadProps extends ComponentProps<"img"> {
name?: string;
Expand All @@ -22,7 +20,6 @@ export const ImageUpload = ({
const ref = useRef<HTMLInputElement>(null);
const { control } = useFormContext();

const upload = useUploadMetadata();
const select = useMutation({
mutationFn: async (file: File) => {
if (file.size >= maxSize) {
Expand All @@ -46,16 +43,10 @@ export const ImageUpload = ({
className={clsx("relative cursor-pointer overflow-hidden", className)}
onClick={() => ref.current?.click()}
>
<IconButton
className="absolute bottom-1 right-1"
disabled={upload.isPending}
icon={upload.isPending ? Spinner : ImageIcon}
/>
<IconButton className="absolute bottom-1 right-1" icon={ImageIcon} />

<div
className={clsx("h-full rounded-xl bg-gray-200 bg-cover bg-center bg-no-repeat", {
"animate-pulse opacity-50": upload.isPending,
})}
className={clsx("h-full rounded-xl bg-gray-200 bg-cover bg-center bg-no-repeat")}
style={{
backgroundImage: `url("${select.data ?? value}")`,
}}
Expand All @@ -72,12 +63,8 @@ export const ImageUpload = ({
const [file] = event.target.files ?? [];
if (file) {
select.mutate(file, {
onSuccess: () => {
upload.mutate(file, {
onSuccess: (data) => {
onChange(data.url);
},
});
onSuccess: (objectUrl) => {
onChange(objectUrl);
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const Tooltip = ({ description }: ITooltipProps): JSX.Element => {
}, [setShowBlock]);

return (
<div className="relative cursor-pointer text-gray-500 dark:text-white">
<div className="relative z-10 cursor-pointer text-gray-500 dark:text-white">
<CiCircleQuestion onMouseEnter={handleShowBlock} onMouseLeave={handleHideBlock} />

{showBlock && (
Expand Down
89 changes: 65 additions & 24 deletions src/features/applications/components/ApplicationButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,35 @@ export const ApplicationButtons = ({

const form = useFormContext<Application>();

const application = useMemo(() => form.getValues(), [form]);
const [
name,
bio,
payoutAddress,
websiteUrl,
profileImageUrl,
bannerImageUrl,
contributionDescription,
impactDescription,
impactCategory,
contributionLinks,
fundingSources,
] = useMemo(
() =>
form.watch([
"name",
"bio",
"payoutAddress",
"websiteUrl",
"profileImageUrl",
"bannerImageUrl",
"contributionDescription",
"impactDescription",
"impactCategory",
"contributionLinks",
"fundingSources",
]),
[form],
);

const checkLinks = (
links: Pick<ContributionLink | ImpactMetrix | FundingSource, "description">[] | undefined,
Expand All @@ -48,20 +76,6 @@ export const ApplicationButtons = ({
links.reduce((prev, link) => prev && link.description !== undefined && link.description.length > 0, true);

const stepComplete = useMemo((): boolean => {
const {
name,
bio,
payoutAddress,
websiteUrl,
profileImageUrl,
bannerImageUrl,
contributionDescription,
impactDescription,
impactCategory,
contributionLinks,
fundingSources,
} = application;

if (step === EApplicationStep.PROFILE) {
return (
bannerImageUrl !== undefined &&
Expand All @@ -76,6 +90,7 @@ export const ApplicationButtons = ({
if (step === EApplicationStep.ADVANCED) {
return (
impactCategory !== undefined &&
impactCategory.length > 0 &&
contributionDescription.length > 0 &&
impactDescription.length > 0 &&
checkLinks(contributionLinks) &&
Expand All @@ -84,15 +99,41 @@ export const ApplicationButtons = ({
}

return true;
}, [step, application]);
}, [
step,
bannerImageUrl,
profileImageUrl,
bio,
name,
payoutAddress,
websiteUrl,
impactCategory,
contributionDescription,
impactDescription,
contributionLinks,
fundingSources,
]);

const handleOnClickNextStep = useCallback(
(event: UIEvent) => {
event.preventDefault();

if (stepComplete) {
onNextStep();
} else {
setShowDialog(true);
}
},
[onNextStep, setShowDialog, stepComplete],
);

const handleOnClickNextStep = useCallback(() => {
if (stepComplete) {
onNextStep();
} else {
setShowDialog(true);
}
}, [onNextStep, setShowDialog, stepComplete]);
const handleOnClickBackStep = useCallback(
(event: UIEvent) => {
event.preventDefault();
onBackStep();
},
[onBackStep],
);

const handleOnOpenChange = useCallback(() => {
setShowDialog(false);
Expand All @@ -109,7 +150,7 @@ export const ApplicationButtons = ({
/>

{step !== EApplicationStep.PROFILE && (
<Button className="text-gray-300 underline" size="auto" variant="ghost" onClick={onBackStep}>
<Button className="text-gray-300 underline" size="auto" variant="ghost" onClick={handleOnClickBackStep}>
Back
</Button>
)}
Expand Down
15 changes: 13 additions & 2 deletions src/features/applications/components/ApplicationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,22 @@ export const ApplicationForm = (): JSX.Element => {
</div>

<div className="mb-4 gap-4 md:flex">
<FormControl required label="Project avatar" name="profileImageUrl">
<FormControl
required
hint="The size should be smaller than 1MB."
label="Project avatar"
name="profileImageUrl"
>
<ImageUpload className="h-48 w-48 " />
</FormControl>

<FormControl required className="flex-1" label="Project background image" name="bannerImageUrl">
<FormControl
required
className="flex-1"
hint="The size should be smaller than 1MB."
label="Project background image"
name="bannerImageUrl"
>
<ImageUpload className="h-48 " />
</FormControl>
</div>
Expand Down
25 changes: 21 additions & 4 deletions src/features/applications/hooks/useCreateApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,25 @@ export function useCreateApplication(options: {
const upload = useUploadMetadata();

const mutation = useMutation({
mutationFn: async (values: Application) =>
Promise.all([
upload.mutateAsync(values).then(({ url: metadataPtr }) =>
mutationFn: async (values: Application) => {
if (!values.bannerImageUrl || !values.profileImageUrl) {
throw new Error("No images included.");
}

const [profileImageFile, bannerImageFile] = await Promise.all([
fetch(values.profileImageUrl),
fetch(values.bannerImageUrl),
]).then(([profileImage, bannerImage]) => Promise.all([profileImage.blob(), bannerImage.blob()]));

const [profileImageUrl, bannerImageUrl] = await Promise.all([
upload.mutateAsync(new File([profileImageFile], "profileImage")),
upload.mutateAsync(new File([bannerImageFile], "bannerImage")),
]);

const metadataValues = { ...values, profileImageUrl: profileImageUrl.url, bannerImageUrl: bannerImageUrl.url };

return Promise.all([
upload.mutateAsync(metadataValues).then(({ url: metadataPtr }) =>
attestation.mutateAsync({
schemaUID: eas.schemas.metadata,
values: {
Expand All @@ -40,7 +56,8 @@ export function useCreateApplication(options: {
},
}),
),
]).then((attestations) => attest.mutateAsync(attestations.map((att) => ({ ...att, data: [att.data] })))),
]).then((attestations) => attest.mutateAsync(attestations.map((att) => ({ ...att, data: [att.data] }))));
},

...options,
});
Expand Down
2 changes: 1 addition & 1 deletion src/pages/applications/confirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ConfirmProjectPage = (): JSX.Element => {
const state = useAppState();

const searchParams = useSearchParams();
const txHash = searchParams.get("txHash");
const txHash = useMemo(() => searchParams.get("txHash"), [searchParams]);
const project = useApplicationByTxHash(txHash ?? "");

const attestation = useMemo(() => project.data, [project]);
Expand Down

0 comments on commit adac895

Please sign in to comment.