Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: integrate application page styling #194

Merged
merged 1 commit into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading