Skip to content

Commit cfbd883

Browse files
tefkah3mcd
andauthored
feat: better indicators in the form (#1349)
* fix: add a lil red star if a form element is required * fix: make it clearer what's going to happen to a form element on save * fix: fix hydration error by not id but elementid * fix: add some autorevalidates * chore: remove button changes * fix: remove thing --------- Co-authored-by: Eric McDaniel <[email protected]>
1 parent d66d3ce commit cfbd883

File tree

5 files changed

+45
-35
lines changed

5 files changed

+45
-35
lines changed

core/app/c/[communitySlug]/forms/[formSlug]/edit/page.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@ export default async function Page(props: {
3131
}) {
3232
const params = await props.params;
3333

34-
const { formSlug, communitySlug } = params;
34+
const { formSlug } = params;
3535

36-
const { user } = await getPageLoginData();
37-
const community = await findCommunityBySlug();
36+
const [{ user }, community] = await Promise.all([getPageLoginData(), findCommunityBySlug()]);
3837

3938
if (!community) {
4039
notFound();
@@ -47,7 +46,7 @@ export default async function Page(props: {
4746
user.id
4847
))
4948
) {
50-
redirect(`/c/${communitySlug}/unauthorized`);
49+
redirect(`/c/${community.slug}/unauthorized`);
5150
}
5251

5352
const communityId = community.id as CommunitiesId;

core/app/components/FormBuilder/FormBuilder.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,7 @@ import {
1414
import { zodResolver } from "@hookform/resolvers/zod";
1515
import { useFieldArray, useForm } from "react-hook-form";
1616

17-
import type {
18-
FormElementsId,
19-
FormsId,
20-
NewFormElements,
21-
NewFormElementToPubType,
22-
Stages,
23-
} from "db/public";
17+
import type { FormElementsId, NewFormElements, NewFormElementToPubType, Stages } from "db/public";
2418
import { formElementsInitializerSchema } from "db/public";
2519
import { logger } from "logger";
2620
import { Form, FormControl, FormField, FormItem } from "ui/form";
@@ -244,7 +238,7 @@ export function FormBuilder({ pubForm, id, stages }: Props) {
244238

245239
const formValues = form.getValues();
246240

247-
useUnsavedChangesWarning(form.formState.isDirty);
241+
useUnsavedChangesWarning(isChanged);
248242

249243
const payload = useMemo(
250244
() => preparePayload({ formValues, defaultValues }),
@@ -274,7 +268,7 @@ export function FormBuilder({ pubForm, id, stages }: Props) {
274268
};
275269
const addElement = useCallback(
276270
(element: FormElementData) => {
277-
append(element);
271+
append({ ...element, added: true });
278272
},
279273
[append]
280274
);
@@ -329,6 +323,8 @@ export function FormBuilder({ pubForm, id, stages }: Props) {
329323
})
330324
);
331325

326+
const dndContextId = React.useId();
327+
332328
return (
333329
<TokenProvider tokens={tokens}>
334330
<BuilderProvider
@@ -385,6 +381,7 @@ export function FormBuilder({ pubForm, id, stages }: Props) {
385381
]}
386382
onDragEnd={handleDragEnd}
387383
sensors={sensors}
384+
id={dndContextId}
388385
>
389386
<SortableContext
390387
items={elements}

core/app/components/FormBuilder/FormElement.tsx

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const FormElement = ({ element, index, isEditing, isDisabled }: FormEleme
3939
const field = pubFields[element.fieldId as PubFieldsId];
4040
const labelName = field
4141
? (element.label ?? (element.config as any)?.label ?? field.name)
42-
: (element.label ?? element.id);
42+
: (element.label ?? element.elementId);
4343

4444
const { openConfigPanel, removeElement, restoreElement } = useBuilder();
4545

@@ -86,8 +86,12 @@ export const FormElement = ({ element, index, isEditing, isDisabled }: FormEleme
8686
"group flex min-h-[76px] flex-1 flex-shrink-0 items-center justify-between gap-3 self-stretch rounded border border-l-[12px] border-solid border-gray-200 border-l-emerald-100 bg-white p-3 pr-4",
8787
isEditing && "border-sky-500 border-l-blue-500",
8888
isDisabled && "cursor-auto opacity-50",
89-
element.deleted && "border-l-red-200",
90-
isDragging && "z-10 cursor-grabbing"
89+
isDragging && "z-10 cursor-grabbing",
90+
{
91+
"border-l-amber-200/70 bg-amber-50/30": element.updated && !element.added,
92+
"border-l-emerald-200 bg-emerald-50/30": element.added,
93+
"border-l-red-200 bg-red-50/30": element.deleted,
94+
}
9195
)}
9296
>
9397
<div className="flex flex-1 flex-shrink-0 flex-wrap justify-start gap-0.5">
@@ -154,16 +158,23 @@ export const FieldInputElement = ({ element, isEditing, labelId }: FieldInputEle
154158
className={cn(
155159
"mr-4 mt-3 shrink-0",
156160
isEditing ? "text-blue-500" : "text-emerald-500",
157-
element.deleted && "text-gray-500"
161+
{
162+
"text-red-300": element.deleted,
163+
"text-amber-500": element.updated && !element.added,
164+
"text-emerald-700": element.added,
165+
}
158166
)}
159167
/>
160168
<div>
161169
<div className="text-gray-500">{field.slug}</div>
162170
<div
163171
id={labelId}
164-
className={cn("font-semibold", element.deleted ? "text-gray-500" : "")}
172+
className={cn("font-semibold", {
173+
"text-gray-500": element.deleted,
174+
})}
165175
>
166176
{(element.config as any)?.label ?? field.name}
177+
{element.required && <span className="text-red-500">* </span>}
167178
</div>
168179
</div>
169180
</>
@@ -179,24 +190,24 @@ const StructuralElement = ({ element, isEditing, labelId }: StructuralElementPro
179190
const { Icon, name } = structuralElements[element.element];
180191

181192
return (
182-
<>
183-
<Icon
184-
size={20}
185-
className={cn(
186-
"mr-4 mt-3 shrink-0",
187-
isEditing ? "text-blue-500" : "text-emerald-500",
188-
element.deleted && "text-gray-500"
189-
)}
190-
/>
191-
<div>
193+
<div>
194+
<div className="flex items-center gap-2">
195+
<Icon
196+
size={20}
197+
className={cn("shrink-0", isEditing ? "text-blue-500" : "text-emerald-500", {
198+
"text-amber-500": element.updated && !element.added,
199+
"text-emerald-700": element.added,
200+
"text-red-300": element.deleted,
201+
})}
202+
/>
192203
<div id={labelId} className="text-gray-500">
193204
{name}
194205
</div>
195-
<div className={cn("prose prose-sm", element.deleted ? "text-gray-500" : "")}>
196-
{/* TODO: sanitize links, truncate, generally improve styles for rendered content*/}
197-
<Markdown className="line-clamp-2">{element.content}</Markdown>
198-
</div>
199206
</div>
200-
</>
207+
<div className={cn("prose prose-sm", element.deleted ? "text-gray-500" : "")}>
208+
{/* TODO: sanitize links, truncate, generally improve styles for rendered content*/}
209+
<Markdown className="line-clamp-2">{element.content}</Markdown>
210+
</div>
211+
</div>
201212
);
202213
};

core/app/components/FormBuilder/actions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ const upsertRelatedPubTypes = async (
2424

2525
if (formElementIds.length) {
2626
// Delete old values
27-
await trx.deleteFrom("_FormElementToPubType").where("A", "in", formElementIds).execute();
27+
await autoRevalidate(
28+
trx.deleteFrom("_FormElementToPubType").where("A", "in", formElementIds)
29+
).execute();
2830
}
2931

3032
// Insert new ones
3133
if (values.length) {
32-
await trx.insertInto("_FormElementToPubType").values(values).execute();
34+
await autoRevalidate(trx.insertInto("_FormElementToPubType").values(values)).execute();
3335
}
3436
};
3537

core/app/components/FormBuilder/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const baseElementSchema = z.object({
1818
rank: z.string(),
1919
deleted: z.boolean().default(false),
2020
updated: z.boolean().default(false),
21+
added: z.boolean().default(false),
2122
configured: z.boolean().default(true),
2223
stageId: z.string().nullable().optional(),
2324
schemaName: z.nativeEnum(CoreSchemaType).nullable().optional(),

0 commit comments

Comments
 (0)