Skip to content

Commit

Permalink
feat: integrate application page styling
Browse files Browse the repository at this point in the history
  • Loading branch information
kittybest committed Jul 12, 2024
1 parent 682321a commit 1d6aae4
Show file tree
Hide file tree
Showing 34 changed files with 1,078 additions and 391 deletions.
3 changes: 3 additions & 0 deletions public/circle-check-blue-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion src/components/EligibilityDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { toast } from "sonner";
import { useAccount, useDisconnect } from "wagmi";

import { useMaci } from "~/contexts/Maci";
import { useAppState } from "~/utils/state";
import { EAppState } from "~/utils/types";

import { Dialog } from "./ui/Dialog";

Expand All @@ -15,6 +17,8 @@ export const EligibilityDialog = (): JSX.Element | null => {
const { onSignup, isEligibleToVote, isRegistered } = useMaci();
const router = useRouter();

const appState = useAppState();

const onError = useCallback(() => toast.error("Signup error"), []);

const handleSignup = useCallback(async () => {
Expand All @@ -38,9 +42,13 @@ export const EligibilityDialog = (): JSX.Element | null => {
router.push("/projects");
}, [router]);

const handleGoToCreateApp = useCallback(() => {
router.push("/applications/new");
}, [router]);

return (
<div>
{isRegistered && (
{isRegistered && appState === EAppState.VOTING && (
<Dialog
button="secondary"
buttonAction={handleGoToProjects}
Expand All @@ -64,6 +72,23 @@ export const EligibilityDialog = (): JSX.Element | null => {
/>
)}

{isRegistered && appState === EAppState.APPLICATION && (
<Dialog
button="secondary"
buttonAction={handleGoToCreateApp}
buttonName="Create Application"
description={
<div className="flex flex-col gap-4">
<p>Start creating your own application now!</p>
</div>
}
isOpen={openDialog}
size="sm"
title="You're all set to apply"
onOpenChange={handleCloseDialog}
/>
)}

{!isRegistered && isEligibleToVote && (
<Dialog
button="secondary"
Expand Down
31 changes: 31 additions & 0 deletions src/components/StatusBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ReactNode } from "react";
import { tv } from "tailwind-variants";

import { createComponent } from "~/components/ui";

const StatusBarContainer = createComponent(
"div",
tv({
base: "flex rounded-md border p-4 justify-center mb-4",
variants: {
status: {
default: "text-blue-600 border-blue-600 bg-blue-400",
pending: "text-[#4E1D0D] border-[#4E1D0D] bg-[#FFEDD5]",
approved: "text-[#14532D] border-[#14532D] bg-[#BBF7D0]",
declined: "text-[#F87171] border-[#F87171] bg-[#FEE2E2]",
},
},
defaultVariants: {
status: "default",
},
}),
);

interface IStatusBarProps {
status: string;
content: ReactNode;
}

export const StatusBar = ({ status, content }: IStatusBarProps): JSX.Element => (
<StatusBarContainer status={status}>{content}</StatusBarContainer>
);
5 changes: 3 additions & 2 deletions src/components/ui/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { createComponent } from ".";
export const Badge = createComponent(
"div",
tv({
base: "inline-flex items-center rounded font-semibold text-gray-500 text-sm",
base: "inline-flex items-center rounded font-semibold text-sm p-2",
variants: {
variant: {
default: "bg-gray-100 dark:bg-gray-800",
success: "dark:bg-green-300 dark:text-green-900",
success: "bg-[#BBF7D0] text-[#14532D]",
pending: "bg-[#FFEDD5] text-[#4E1D0D]",
},
size: {
md: "px-1 ",
Expand Down
35 changes: 28 additions & 7 deletions src/components/ui/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@ export const Select = createComponent(
export const Checkbox = createComponent(
"input",
tv({
base: [...inputBase, "checked:focus:dark:bg-gray-700 checked:hover:dark:bg-gray-700"],
base: [...inputBase, "rounded-none checked:focus:dark:bg-gray-700 checked:hover:dark:bg-gray-700"],
}),
);

export const Label = createComponent(
"label",
tv({
base: "block tracking-wider dark:text-gray-300 font-semibold",
variants: { required: { true: "after:content-['*']" } },
variants: {
required: {
true: "after:content-['*'] after:text-blue-400",
false: "after:content-['(optional)'] after:text-gray-300 after:text-sm after:font-semibold after:ml-1",
},
},
}),
);

Expand Down Expand Up @@ -98,7 +103,7 @@ export const FormControl = ({
const error = index && errors[index];

return (
<fieldset className={cn("mb-4", className)}>
<fieldset className={cn(className)}>
{label && (
<Label className="mb-1" htmlFor={name} required={required}>
{label}
Expand All @@ -119,9 +124,13 @@ export const FormControl = ({
};

export const FieldArray = <S extends z.Schema>({
title,
description,
name,
renderField,
}: {
title: string;
description: string;
name: string;
renderField: (field: z.infer<S>, index: number) => ReactNode;
}): JSX.Element => {
Expand All @@ -135,6 +144,14 @@ export const FieldArray = <S extends z.Schema>({

return (
<div className="mb-8">
<p>
{title}

<span className="text-blue-400">*</span>
</p>

<p className="mb-2 text-gray-300">{description}</p>

{error && <div className="border-red-900 dark:text-red-500 border p-2">{String(error)}</div>}

{fields.map((field, i) => (
Expand All @@ -155,18 +172,21 @@ export const FieldArray = <S extends z.Schema>({
</div>
))}

<div className="flex justify-end">
<div className="flex justify-start">
<IconButton
icon={PlusIcon}
size="sm"
type="button"
variant="outline"
onClick={() => {
append({});
}}
>
Add row
</IconButton>
</div>

<div className="mt-4 h-[1px] w-full bg-gray-100" />
</div>
);
};
Expand All @@ -175,11 +195,12 @@ export const FormSection = ({
title,
description,
children,
...props
}: { title: string; description: string } & ComponentProps<"section">): JSX.Element => (
<section className="mb-8">
<h3 className="mb-1 text-xl font-semibold">{title}</h3>
<section className="mb-8" {...props}>
<h3 className="mb-1 font-sans text-xl font-semibold">{title}</h3>

<p className="mb-4 leading-loose text-gray-600 dark:text-gray-400">{description}</p>
<p className="mb-4 leading-loose text-gray-400">{description}</p>

{children}
</section>
Expand Down
1 change: 1 addition & 0 deletions src/components/ui/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const inputBase = [
"border-gray-200",
"rounded-lg",
"border",
"placeholder:text-gray-300",
];

export const Input = createComponent(
Expand Down
36 changes: 36 additions & 0 deletions src/features/applications/components/AdminButtonsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useCallback } from "react";

import { Button } from "~/components/ui/Button";
import { useIsAdmin } from "~/hooks/useIsAdmin";
import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork";

import { useApproveApplication } from "../hooks/useApproveApplication";

interface IAdminButtonsBarProps {
projectId: string;
}

export const AdminButtonsBar = ({ projectId }: IAdminButtonsBarProps): JSX.Element => {
const isAdmin = useIsAdmin();
const { isCorrectNetwork, correctNetwork } = useIsCorrectNetwork();

const approve = useApproveApplication({});

const onClick = useCallback(() => {
approve.mutate([projectId]);
}, [approve, projectId]);

return (
<div className="my-3 flex justify-end gap-2">
<Button
suppressHydrationWarning
disabled={!isAdmin || !isCorrectNetwork}
size="auto"
variant="primary"
onClick={onClick}
>
{!isCorrectNetwork ? `Connect to ${correctNetwork.name}` : "Approve application"}
</Button>
</div>
);
};
136 changes: 136 additions & 0 deletions src/features/applications/components/ApplicationButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useMemo, useCallback, useState } from "react";
import { useFormContext } from "react-hook-form";
import { useAccount } from "wagmi";

import { Button, IconButton } from "~/components/ui/Button";
import { Dialog } from "~/components/ui/Dialog";
import { Spinner } from "~/components/ui/Spinner";
import { useIsCorrectNetwork } from "~/hooks/useIsCorrectNetwork";

import type { Application } from "../types";
import type { ImpactMetrix, ContributionLink, FundingSource } from "~/features/projects/types";

export enum EApplicationStep {
PROFILE,
ADVANCED,
REVIEW,
}

interface IApplicationButtonsProps {
step: EApplicationStep;
isUploading: boolean;
isPending: boolean;
onNextStep: () => void;
onBackStep: () => void;
}

export const ApplicationButtons = ({
step,
isUploading,
isPending,
onNextStep,
onBackStep,
}: IApplicationButtonsProps): JSX.Element => {
const { isCorrectNetwork } = useIsCorrectNetwork();

const { address } = useAccount();

const [showDialog, setShowDialog] = useState<boolean>(false);

const form = useFormContext<Application>();

const application = useMemo(() => form.getValues(), [form]);

const checkLinks = (links: Pick<ContributionLink | ImpactMetrix | FundingSource, "description">[]): boolean =>
links.reduce((prev, link) => prev && link.description.length > 0, true);

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

if (step === EApplicationStep.PROFILE) {
return (
bannerImageUrl !== undefined &&
profileImageUrl !== undefined &&
bio.length > 0 &&
name.length > 0 &&
payoutAddress.length > 0 &&
websiteUrl.length > 0
);
}

if (step === EApplicationStep.ADVANCED) {
return (
impactCategory !== undefined &&
contributionDescription.length > 0 &&
impactDescription.length > 0 &&
checkLinks(contributionLinks) &&
checkLinks(impactMetrics) &&
checkLinks(fundingSources)
);
}

return true;
}, [step, application]);

const handleOnClickNextStep = useCallback(() => {
if (stepComplete) {
onNextStep();
} else {
setShowDialog(true);
}
}, [onNextStep, setShowDialog, stepComplete]);

const handleOnOpenChange = useCallback(() => {
setShowDialog(false);
}, [setShowDialog]);

return (
<div className="flex justify-end gap-2">
<Dialog
description="There are still some inputs not fulfilled, please complete all the required information."
isOpen={showDialog}
size="sm"
title="Please complete all the required information"
onOpenChange={handleOnOpenChange}
/>

{step !== EApplicationStep.PROFILE && (
<Button className="text-gray-300 underline" size="auto" variant="ghost" onClick={onBackStep}>
Back
</Button>
)}

{step !== EApplicationStep.REVIEW && (
<Button size="auto" variant="primary" onClick={handleOnClickNextStep}>
Next
</Button>
)}

{step === EApplicationStep.REVIEW && (
<IconButton
disabled={isPending || !address || !isCorrectNetwork}
icon={isPending ? Spinner : null}
isLoading={isPending}
size="auto"
type="submit"
variant="primary"
>
{isUploading ? "Uploading metadata" : "Submit"}
</IconButton>
)}
</div>
);
};
Loading

0 comments on commit 1d6aae4

Please sign in to comment.