This repository has been archived by the owner on Feb 10, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* adds Select * adds markdownEditor * add file upload * progres bar improvement * adds modular form and custom controled reused fields * adds index db persist feature * removed pnpm-lock file * fix build * fix build * hooks - lib refactoring * minor ui updates * modular form refactoring * creates the address allowlist component * creates the applicationQuestions form component * creates the fieldArray form component * creates the Select form component * creates the FileUpload form component * creates the MarkdownEditor form component * creates the Metrics form component * creates the Round-dates form component * creates the DisabledProgram field form component * adds the updated story configuration * cleaning * renamed lib/form to lib/indexDB
- Loading branch information
Showing
68 changed files
with
3,261 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Meta, StoryObj } from "@storybook/react"; | ||
|
||
import { Form, FormProps } from "@/components/Form"; | ||
import { FormField } from "@/components/Form/types/fieldTypes"; | ||
|
||
const fields: FormField[] = [ | ||
{ | ||
field: { | ||
name: "roundName", | ||
label: "Round name", | ||
className: "border-grey-300", | ||
validation: { stringValidation: { minLength: 7 } }, | ||
}, | ||
component: "Input", | ||
placeholder: "your cool round name", | ||
}, | ||
|
||
{ | ||
field: { | ||
name: "roundDescription", | ||
label: "Round description", | ||
validation: { required: true }, | ||
}, | ||
component: "MarkdownEditor", | ||
}, | ||
{ | ||
field: { | ||
name: "select", | ||
label: "Select", | ||
validation: { required: true }, | ||
}, | ||
component: "Select", | ||
options: [ | ||
{ | ||
items: [ | ||
{ label: "Apple", value: "apple" }, | ||
{ label: "Banana", value: "banana" }, | ||
], | ||
}, | ||
{ | ||
items: [ | ||
{ label: "Carrot", value: "carrot" }, | ||
{ label: "Lettuce", value: "lettuce" }, | ||
], | ||
}, | ||
], | ||
placeholder: "Select", | ||
variant: "filled", | ||
size: "md", | ||
}, | ||
{ | ||
field: { | ||
name: "fileUpload", | ||
label: "File upload", | ||
validation: { required: true }, | ||
}, | ||
component: "FileUpload", | ||
mimeTypes: ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/svg+xml"], | ||
}, | ||
]; | ||
|
||
export default { | ||
title: "Components/Form", | ||
component: Form, | ||
} as Meta; | ||
type Story = StoryObj<FormProps>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
fields, | ||
persistKey: "storybook-form", | ||
}, | ||
render: (args) => { | ||
return ( | ||
<div> | ||
<Form {...args} /> | ||
</div> | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { ForwardedRef, forwardRef, useImperativeHandle } from "react"; | ||
import { FormProvider, useFormContext } from "react-hook-form"; | ||
|
||
import { useFormWithPersist } from "@/hooks"; | ||
|
||
import { FormField } from "./types/fieldTypes"; | ||
import { buildSchemaFromFields } from "./utils/buildSchemaFromFields"; | ||
import { componentRegistry } from "./utils/componentRegistry"; | ||
|
||
export interface FormProps { | ||
persistKey: string; | ||
fields: FormField[]; | ||
defaultValues?: any; | ||
dbName: string; | ||
storeName: string; | ||
} | ||
|
||
export const Form = forwardRef(function Form( | ||
{ persistKey, fields, defaultValues, dbName, storeName }: FormProps, | ||
ref: ForwardedRef<{ isFormValid: () => Promise<boolean> }>, | ||
) { | ||
const schema = buildSchemaFromFields(fields); | ||
|
||
const form = useFormWithPersist({ schema, defaultValues, persistKey, dbName, storeName }); | ||
|
||
useImperativeHandle(ref, () => ({ | ||
isFormValid: async () => { | ||
try { | ||
const isValid = await form.trigger(); // Trigger validation on all fields | ||
if (!isValid) return false; | ||
|
||
return true; | ||
} catch (error) { | ||
console.error(error); | ||
return false; | ||
} | ||
}, | ||
})); | ||
|
||
return ( | ||
<FormProvider {...form}> | ||
<form onSubmit={form.handleSubmit(() => void 0)} className="flex flex-col gap-4"> | ||
{fields.map((field) => ( | ||
<FormControl key={field.field.name} field={field} /> | ||
))} | ||
</form> | ||
</FormProvider> | ||
); | ||
}); | ||
function FormControl({ field }: { field: FormField }) { | ||
const { | ||
register, | ||
formState: { errors }, | ||
control, | ||
} = useFormContext(); | ||
|
||
type FieldOfType = Extract<FormField, { component: typeof field.component }>; | ||
const { field: fieldProps, ...componentProps } = field as FieldOfType; | ||
|
||
const registryEntry = componentRegistry[field.component]; | ||
const { Component, isControlled } = registryEntry; | ||
|
||
const props = isControlled | ||
? { ...componentProps, name: fieldProps.name, control } | ||
: { ...componentProps, ...register(fieldProps.name) }; | ||
|
||
return ( | ||
<div className="flex flex-col justify-center gap-2"> | ||
<div className="flex items-center justify-between"> | ||
<label htmlFor={fieldProps.name} className="block text-[14px]/[20px] font-medium"> | ||
{fieldProps.label} | ||
</label> | ||
{fieldProps.validation?.required && ( | ||
<div className="text-[14px]/[16px] text-moss-700">*Required</div> | ||
)} | ||
</div> | ||
<Component {...props} /> | ||
{errors[fieldProps.name]?.message && ( | ||
<p className="text-sm text-red-300">{String(errors[fieldProps.name]?.message)}</p> | ||
)} | ||
</div> | ||
); | ||
} |
100 changes: 100 additions & 0 deletions
100
src/components/Form/FormControllers/AllowlistFormController/AllowlistFormController.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// src/components/Form/FormAllowlistController.tsx | ||
import React, { useRef } from "react"; | ||
import { useFormContext, Controller } from "react-hook-form"; | ||
|
||
import { UploadIcon } from "@heroicons/react/solid"; | ||
import Papa from "papaparse"; | ||
import { getAddress, isAddress } from "viem"; | ||
|
||
import { Button } from "@/primitives/Button"; | ||
import { TextArea } from "@/primitives/TextArea"; | ||
|
||
export interface AllowlistFormControllerProps { | ||
/** The name of the form field */ | ||
name: string; | ||
} | ||
|
||
export const AllowlistFormController: React.FC<AllowlistFormControllerProps> = ({ name }) => { | ||
const { control, setValue } = useFormContext(); | ||
const fileInputRef = useRef<HTMLInputElement>(null); | ||
|
||
/** | ||
* Handles CSV file upload and extracts valid addresses | ||
* @param event - The input change event | ||
*/ | ||
const handleCSVUpload = (event: React.ChangeEvent<HTMLInputElement>): void => { | ||
const file = event.target.files?.[0]; | ||
if (!file) return; | ||
|
||
Papa.parse(file, { | ||
complete: ({ data }) => { | ||
const uniqueAddresses = Array.from( | ||
new Set( | ||
data | ||
.flat() | ||
.filter((item): item is string => typeof item === "string" && isAddress(item.trim())), | ||
), | ||
).join(","); | ||
|
||
const formattedAddresses = uniqueAddresses.split(",").map((addr) => getAddress(addr)); | ||
|
||
setValue(name, formattedAddresses); | ||
}, | ||
skipEmptyLines: true, | ||
}); | ||
|
||
// Reset the input value to allow re-uploading the same file if needed | ||
event.target.value = ""; | ||
}; | ||
|
||
return ( | ||
<div className="flex flex-col gap-4"> | ||
<div className="flex justify-start"> | ||
<Button | ||
value="Import CSV" | ||
className="bg-grey-100 text-grey-900" | ||
icon={<UploadIcon className="size-4 text-grey-900" />} | ||
onClick={() => fileInputRef.current?.click()} | ||
/> | ||
<input | ||
type="file" | ||
id="csv-upload" | ||
className="hidden" | ||
accept=".csv" | ||
onChange={handleCSVUpload} | ||
ref={fileInputRef} | ||
/> | ||
</div> | ||
|
||
<Controller | ||
name={name} | ||
control={control} | ||
render={({ field }) => ( | ||
<TextArea | ||
value={field.value} | ||
onChange={(e) => field.onChange(e.target.value.split(","))} | ||
placeholder="Enter all the addresses as a comma-separated list here..." | ||
className="min-h-52 w-full" | ||
/> | ||
)} | ||
/> | ||
|
||
<p className="text-sm text-grey-500"> | ||
Enter all the addresses as a comma-separated list below. Duplicates and invalid addresses | ||
will automatically be removed. | ||
</p> | ||
</div> | ||
); | ||
}; | ||
|
||
/** | ||
* Utility function to validate a list of addresses | ||
* @param addresses - A comma-separated string of addresses | ||
* @returns An array of valid, trimmed addresses | ||
*/ | ||
export const validateAddresses = (addresses: string): string[] => { | ||
return addresses | ||
.split(",") | ||
.map((addr) => addr.trim()) | ||
.filter((addr) => isAddress(addr)); | ||
}; |
1 change: 1 addition & 0 deletions
1
src/components/Form/FormControllers/AllowlistFormController/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./AllowlistFormController"; |
Oops, something went wrong.