chore: change create form to universal and other devex fixes

This commit is contained in:
2024-12-17 16:45:28 +01:00
parent a880f4f042
commit e51d64a2cf
5 changed files with 106 additions and 133 deletions

View File

@@ -1,97 +1,48 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useFormState } from 'react-dom';
import { createSchema, createSchemaType } from '@/lib/forms/zod';
import { create } from '@/lib/forms/actions';
import React from 'react';
import { useRouter } from 'next/navigation';
import SubmitButton from '@/components/app/SubmitButton/SubmitButton';
import { UniversalForm } from '@/components/app/UniversalForm/UniversalForm';
// TODO: move form to the new universal form component
export default function ProfileForm() {
export default function CreateForm() {
const router = useRouter();
const form = useForm<createSchemaType>({
resolver: zodResolver(createSchema),
defaultValues: {
name: '',
description: '',
github: '',
},
});
const [formState, formAction] = useFormState(create, null);
React.useEffect(() => {
if (formState && formState.id) {
router.push(`/project/${formState.id}`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formState]);
return (
<div className="min-h-[calc(100vh-4rem)] flex items-center justify-center">
<div className="max-w-md w-full p-4">
<Form {...form}>
<form
action={formAction}
onSubmit={form.handleSubmit((data) => {
const formData = new FormData();
Object.entries(data).forEach(([key, value]) => {
formData.append(key, value);
});
formAction(formData);
})}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Project name</FormLabel>
<FormControl>
<Input placeholder="Echospace" {...field} />
</FormControl>
<FormDescription>How the project is called.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Input placeholder="A developer-centric user feedback platform." {...field} />
</FormControl>
<FormDescription>Describe the project a bit.</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="github"
render={({ field }) => (
<FormItem>
<FormLabel>Github</FormLabel>
<FormControl>
<Input placeholder="https://github.com/SrIzan10/echospace" {...field} />
</FormControl>
<FormDescription>Your Github repository link. Will come in handy for some integrations!</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<SubmitButton buttonText='Submit' className='w-full' />
</form>
</Form>
</div>
<UniversalForm
action={create}
schemaName="create"
fields={[
{
name: 'name',
label: 'Project name',
placeholder: 'Echospace',
description: 'How the project is called.',
},
{
name: 'description',
label: 'Description',
placeholder: 'A developer-centric user feedback platform.',
description: 'Describe the project a bit.',
},
{
name: 'github',
label: 'Github',
placeholder: 'https://github.com/SrIzan10/echospace',
},
]}
submitText="Submit"
submitClassname="w-full"
onActionComplete={(res) => {
// @ts-ignore yea
if (res && res.id) {
// @ts-ignore i stopped caring
router.push(`/project/${res.id}`);
}
}}
/>
</div>
</div>
);
}
}

View File

@@ -11,17 +11,19 @@ export default async function Page() {
userId: user!.id,
},
});
if (db.length === 0) {
return (
<div className="text-center flex flex-col gap-4 pt-4">
<h2>No projects found</h2>
<p className="text-muted-foreground">Create a project to get started</p>
<Link href="/create">
<Button size={'sm'}>Create Project</Button>
</Link>
</div>
);
}
return (
<div className="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 p-4">
{db.length === 0 && (
<div className="col-span-full text-center">
<h1 className="text-2xl font-bold">No projects found</h1>
<p className="text-muted-foreground">Create a project to get started</p>
<Link href="/create">
<Button size={'sm'}>Create Project</Button>
</Link>
</div>
)}
{db.map((d) => (
<ProjectCard key={d.id} {...d} />
))}

View File

@@ -18,7 +18,7 @@ import {
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
} from '@/components/ui/breadcrumb';
// TODO: refactor to maybe append the no feedback message to the table div
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
@@ -30,23 +30,29 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
});
if (!project) {
return <div>Project not found</div>;
return (
<>
<h2>Project not found</h2>
<p>...or maybe you don&apos;t have permission!</p>
</>
);
}
// maybe it's not clean enough but who cares
return (
<div className="p-4">
<div className="max-w-4xl mx-auto">
<Breadcrumb className='pb-5'>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/dashboard">Dashboard</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>{project.name}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<Breadcrumb className="pb-5">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/dashboard">Dashboard</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>{project.name}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<div className="flex items-center justify-between w-full pb-10">
<h2>{project.name}</h2>
<p className="text-muted-foreground ml-2 truncate">{project.description}</p>
@@ -54,16 +60,17 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
<Button>Settings</Button>
</Link>
</div>
{project.feedback.length === 0 ? (
<div className="border rounded-lg max-h-[32rem] overflow-y-auto text-center p-10">
<h2>No feedback!</h2>
<p className="text-muted-foreground mt-2">
Once you start receiving feedback, it will appear here.
</p>
</div>
) : (
<div className="border rounded-lg max-h-[32rem] overflow-y-auto">
<Table>
<div className="border rounded-lg max-h-[32rem] overflow-y-auto">
{project.feedback.length === 0 && (
<div className="flex flex-col justify-center items-center p-10">
<h2>No feedback!</h2>
<p className="text-muted-foreground mt-2">
Once you start receiving feedback, it will appear here.
</p>
</div>
)}
<Table>
{project.feedback.length > 0 && (
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">ID</TableHead>
@@ -73,21 +80,25 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
))}
</TableRow>
</TableHeader>
<TableBody>
{/* using toReversed to not change the upstream array in case of other data treatments needed */}
{project.feedback.toReversed().map((feedback) => (
<TableRow key={feedback.id}>
<TableCell>{feedback.id}</TableCell>
<TableCell>{feedback.message}</TableCell>
{Object.entries(JSON.parse(feedback.customData)).map(([key, value]) => (
<TableCell key={key}>{value as string}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
)}
<TableBody>
{/*
using toReversed to not change the upstream array in case of
other data treatments needed.
why js why
*/}
{project.feedback.toReversed().map((feedback) => (
<TableRow key={feedback.id}>
<TableCell>{feedback.id}</TableCell>
<TableCell>{feedback.message}</TableCell>
{Object.entries(JSON.parse(feedback.customData)).map(([key, value]) => (
<TableCell key={key}>{value as string}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
);

View File

@@ -21,19 +21,23 @@ import SubmitButton from '../SubmitButton/SubmitButton';
import { useFormState } from 'react-dom';
import React from 'react';
import { toast } from 'sonner';
import { createSchema } from '@/lib/forms/zod';
export const schemaDb = [
{ name: 'projectSettings', zod: projectSettingsSchema },
{ name: 'ratelimitChange', zod: ratelimitChangeSchema },
{ name: 'customData', zod: customDataSchema },
{ name: 'create', zod: createSchema },
] as const;
export function UniversalForm<T extends z.ZodType>({
fields,
schemaName,
action,
onActionComplete,
defaultValues,
submitText = 'Submit',
submitClassname,
}: UniversalFormProps<T>) {
const [state, formAction] = useFormState(action, null);
const schema = schemaDb.find((s) => s.name === schemaName)?.zod;
@@ -54,6 +58,9 @@ export function UniversalForm<T extends z.ZodType>({
// @ts-ignore
toast.error(state.error);
}
if (state) {
onActionComplete?.(state);
}
}, [state]);
return (
@@ -81,7 +88,7 @@ export function UniversalForm<T extends z.ZodType>({
)}
/>
))}
<SubmitButton buttonText={submitText} />
<SubmitButton buttonText={submitText} className={submitClassname} />
</form>
</Form>
);

View File

@@ -15,6 +15,8 @@ export type UniversalFormProps<T extends z.ZodType> = {
fields: FormFieldConfig[];
schemaName: typeof schemaDb[number]['name'];
action: (prev: any, formData: FormData) => void;
onActionComplete?: (result: unknown) => void;
defaultValues?: Partial<z.infer<T>>;
submitText?: string;
submitClassname?: string;
};