Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6fb1a71
new elemetn added
Jan 23, 2025
1ec7b06
all files
Jan 23, 2025
94aa252
added element and documenation
Jan 23, 2025
a72fa05
added image upload
Jan 23, 2025
28f20df
added rating scale
Jan 23, 2025
c721521
clean build
Jan 23, 2025
f417d15
theming
Jan 23, 2025
e92dc0b
Centered image upload
bwedding Jan 26, 2025
f6af8a3
Dual image upload
bwedding Jan 26, 2025
820fce6
Image select
bwedding Jan 26, 2025
98640b7
Image select fixed width issue
bwedding Jan 26, 2025
e4d5fc8
removed example
bwedding Jan 26, 2025
bfb0e8a
compiles clean now
bwedding Jan 26, 2025
afb8a4b
Setup local db. Is saving
bwedding Jan 26, 2025
07485ca
fix up a few errors
bwedding Jan 26, 2025
46b99ca
fix up a few errors
bwedding Jan 26, 2025
40f1aad
Increased size to save images and added code to save images to db
bwedding Jan 26, 2025
3726e80
Added ability to embed forms
bwedding Jan 26, 2025
a0e5503
Fixed all errors and warnings
bwedding Jan 28, 2025
04bb043
Lots of layout changes
bwedding Jan 28, 2025
7aaa4a2
Added image element to layout
bwedding Jan 28, 2025
f54c644
Seemingly works ok
bwedding Jan 28, 2025
603747b
Cleaning up layout elements
bwedding Jan 28, 2025
024f9c6
Added 2 column layout
bwedding Jan 28, 2025
832654c
Fixed 2 col grid saveing
bwedding Jan 28, 2025
2b3bbf3
Fixing up the element parsing
bwedding Jan 29, 2025
d3f1eca
Fixed build errors
bwedding Jan 29, 2025
ed1c8cc
Fixed build more errors
bwedding Jan 29, 2025
8b9f43f
Fixed audit errors and typescript errors
brucewedding Jan 29, 2025
d06bb62
Fixed editability of the 2 column elements
brucewedding Jan 29, 2025
4b7f680
Fixed restoring of 2 column
brucewedding Jan 29, 2025
8c3e2ab
Fixed loading theme in designer as well as helper text background
brucewedding Jan 30, 2025
4cd5e39
Fixed embedded preview
brucewedding Jan 30, 2025
0c8c079
Fixing rendering preview of embedded
brucewedding Jan 30, 2025
ceee062
Lot of refactoring
bwedding Jan 30, 2025
694a547
Added all fields to the embedded form and preview
brucewedding Jan 31, 2025
96b8616
Starting on multi-page. Designer and preview work great
brucewedding Jan 31, 2025
7fe5c97
Embedded seems to be working
brucewedding Jan 31, 2025
79a9256
Upgraded a few older packages
brucewedding Feb 1, 2025
b7cb0d5
Added moon
brucewedding Feb 1, 2025
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
7 changes: 3 additions & 4 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard

POSTGRES_PRISMA_URL="postgres://default:5YBlPxAmaN4c@ep-morning-smoke-a4r3sc7j-pooler.us-east-1.aws.neon.tech:5432/verceldb?sslmode=require&pgbouncer=true&connect_timeout=15"
POSTGRES_URL_NON_POOLING="postgres://default:5YBlPxAmaN4c@ep-morning-smoke-a4r3sc7j.us-east-1.aws.neon.tech:5432/verceldb?sslmode=require"

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"
POSTGRES_PRISMA_URL="postgresql://postgres:Monteria80@localhost:5432/quickform?schema=public"
POSTGRES_URL_NON_POOLING="postgresql://postgres:Monteria80@localhost:5432/quickform?schema=public"
DATABASE_URL="postgresql://postgres:Monteria80@localhost:5432/quickform?schema=public"
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"sarif-viewer.connectToGithubCodeScanning": "off"
}
304 changes: 304 additions & 0 deletions Docs/AddElement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
# Adding a New Form Element

This guide explains how to add a new form element type to the Quick Form Builder system.

## Overview

Form elements are the building blocks of forms in our system. Each element type (like text fields, checkboxes, etc.) is implemented as a separate component that follows a consistent pattern and interface.

## Element Categories

Elements are divided into two categories in the sidebar:

1. **Layout Elements**: Static elements used for form structure and presentation (e.g., Title, SubTitle, Paragraph, Separator, Spacer, Image)
2. **Form Elements**: Interactive input elements that collect data (e.g., TextField, NumberField, ImageUpload)

When adding a new element, make sure to place it in the appropriate section in `FormElementsSidebar.tsx`.

## TypeScript Best Practices

### Image Handling
When working with images in TypeScript, avoid using the global `Image` constructor as it can conflict with imported icon components. Instead:

```typescript
// ❌ Don't use this
const img = new Image();

// ✅ Use this instead
const img = document.createElement('img') as HTMLImageElement;
```

### Form Element Type Definitions
Always define proper types for your element's attributes:

```typescript
type CustomInstance = FormElementInstance & {
extraAttributes: {
// Define your element's specific attributes here
label: string;
required: boolean;
// ... other attributes
};
};

// Use zod for runtime validation
const propertiesSchema = z.object({
label: z.string().min(2).max(50),
required: z.boolean().default(false),
// ... other validations
});
```

### Property Updates
Always use the `useDesigner` hook to update element properties. Direct mutations of `extraAttributes` will not trigger re-renders or persist changes:

```typescript
function PropertiesComponent({ elementInstance }: { elementInstance: FormElementInstance }) {
const element = elementInstance as CustomInstance;
const { updateElement } = useDesigner();
const form = useForm<propertiesFormSchemaType>({
resolver: zodResolver(propertiesSchema),
mode: "onBlur",
defaultValues: element.extraAttributes,
});

// ❌ Don't update properties directly
const wrongWay = () => {
element.extraAttributes.someProperty = newValue; // This won't work properly
};

// ✅ Use updateElement from useDesigner
const rightWay = (values: propertiesFormSchemaType) => {
updateElement(element.id, {
...element,
extraAttributes: {
...element.extraAttributes,
...values,
},
});
};

return (
<Form {...form}>
<form
onBlur={form.handleSubmit(rightWay)}
onSubmit={(e) => {
e.preventDefault();
}}
className="space-y-3"
>
{/* Form fields here */}
</form>
</Form>
);
}
```

## Step-by-Step Guide

### 1. Add Element Type Definition

In `src/components/FormElements.tsx`, add your new element type to the `ElementsType` union type:

```typescript
export type ElementsType =
| "TextField"
| "TitleField"
// ... other existing types ...
| "YourNewFieldType"; // Add your new type here
```

### 2. Create Element Component File

Create a new file for your element under `src/components/field/` (e.g., `YourNewField.tsx`). The file should include:

#### a) Type Definitions
```typescript
type CustomInstance = FormElementInstance & {
extraAttributes: {
label: string;
helperText: string;
required: boolean;
placeholder: string;
// Add any additional attributes your element needs
};
};

const propertiesSchema = z.object({
label: z.string().min(2).max(50),
helperText: z.string().max(200),
required: z.boolean().default(false),
placeholder: z.string().max(50),
// Add validation for any additional properties
});
```

#### b) Element Implementation
```typescript
export const YourNewFormElement: FormElement = {
type: "YourNewFieldType",

// Factory function to create new instances
construct: (id: string) => ({
id,
type: "YourNewFieldType",
extraAttributes: {
label: "Default Label",
helperText: "Default helper text",
required: false,
placeholder: "Default placeholder",
},
}),

// Sidebar button configuration
designerBtnElement: {
icon: YourIcon, // Import from react-icons
label: "Your Element",
},

// Component references
designerComponent: DesignerComponent,
formComponent: FormComponent,
propertiesComponent: PropertiesComponent,

// Validation logic
validate: (formElement: FormElementInstance, currentValue: string): boolean => {
const element = formElement as CustomInstance;
if (element.extraAttributes.required) {
return currentValue.length > 0;
}
return true;
},
};
```

#### c) Required Components

1. Designer Component (Preview in builder):
```typescript
function DesignerComponent({ elementInstance }: { elementInstance: FormElementInstance }) {
const element = elementInstance as CustomInstance;
const { label, helperText, required, placeholder } = element.extraAttributes;
return (
<div className="flex flex-col gap-2 w-full">
<Label>
{label}
{required && "*"}
</Label>
<Input readOnly disabled placeholder={placeholder} />
{helperText && <p className="text-muted-foreground text-[0.8rem]">{helperText}</p>}
</div>
);
}
```

2. Form Component (Live form element):
```typescript
function FormComponent({
elementInstance,
submitValue,
isInvalid,
defaultValue,
}: {
elementInstance: FormElementInstance;
submitValue?: (key: string, value: string) => void;
isInvalid?: boolean;
defaultValue?: string;
}) {
const element = elementInstance as CustomInstance;
// Implementation for the actual form input
}
```

3. Properties Component (Settings panel):
```typescript
function PropertiesComponent({ elementInstance }: { elementInstance: FormElementInstance }) {
const element = elementInstance as CustomInstance;
const { updateElement } = useDesigner();
const form = useForm<propertiesFormSchemaType>({
resolver: zodResolver(propertiesSchema),
mode: "onBlur",
defaultValues: element.extraAttributes,
});

// ❌ Don't update properties directly
const wrongWay = () => {
element.extraAttributes.someProperty = newValue; // This won't work properly
};

// ✅ Use updateElement from useDesigner
const rightWay = (values: propertiesFormSchemaType) => {
updateElement(element.id, {
...element,
extraAttributes: {
...element.extraAttributes,
...values,
},
});
};

return (
<Form {...form}>
<form
onBlur={form.handleSubmit(rightWay)}
onSubmit={(e) => {
e.preventDefault();
}}
className="space-y-3"
>
{/* Form fields here */}
</form>
</Form>
);
}
```

### 3. Register the Element

In `src/components/FormElements.tsx`, import and add your element to the FormElements object:

```typescript
import { YourNewFormElement } from "./field/YourNewField";

export const FormElements: FormElementsType = {
TextField: TextFieldFormElement,
// ... other elements ...
YourNewFieldType: YourNewFormElement,
};
```

### 4. Add to Sidebar

In `src/components/FormElementsSidebar.tsx`, add your element to the sidebar:

```typescript
<SidebarBtnElement formElement={FormElements.YourNewFieldType} />
```

## Database Integration

No additional database changes are required. The system stores form content as JSON in the Prisma database, and new elements are automatically handled as long as they follow the FormElementInstance interface.

## Testing Your New Element

1. Create a new form
2. Verify your element appears in the sidebar
3. Test dragging and dropping the element
4. Test property editing
5. Test form saving and loading
6. Test form publishing and submission

## Best Practices

1. Follow the existing naming conventions
2. Implement proper validation
3. Include helpful default values
4. Add appropriate TypeScript types
5. Ensure proper error handling
6. Test all component states (preview, edit, submit)

## Common Issues

- If the element doesn't appear in the sidebar, check the FormElements export
- If properties don't update, verify the PropertiesComponent implementation
- If validation fails, check the validate function implementation
Loading