Skip to content

Commit 47ac46c

Browse files
authored
feat: better pubtype editor (#1333)
* feat: initial implementation of type page design * feat: better pubtype editor * feat: add initial rank support to field in types * fix: fix seed test * fix: make sure migration runs properly in both reset and migrate * feat: add typed ID string util for typebox * fix: improve type updating logic * refactor: reorganize * fix: fix things * feat: allow updating of pubtype * refactor: clean up type title * fix: dont use name in builder and only show correct pubfields in type editor * fix: fix type error * fix: fix pubtype tests * chore: formaT * fiX: properly order pubtypes * fix: better order for token page * fix: fix formbuilder test
1 parent 3fe3888 commit 47ac46c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1976
-903
lines changed

core/app/c/[communitySlug]/types/CreatePubType.tsx

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import { useCallback, useState } from "react";
4+
import { useRouter } from "next/navigation";
5+
6+
import type { PubTypesId } from "db/public";
7+
import { Button } from "ui/button";
8+
import { Dialog, DialogContent, DialogOverlay, DialogTitle, DialogTrigger } from "ui/dialog";
9+
import { Plus } from "ui/icon";
10+
import { cn } from "utils";
11+
12+
import { Footer } from "~/app/components/CreateEditDialog";
13+
import { useCommunity } from "~/app/components/providers/CommunityProvider";
14+
import { NewTypeForm } from "./NewTypeForm";
15+
16+
export const CreatePubTypeButton = ({ className }: { className?: string }) => {
17+
const [isOpen, setIsOpen] = useState(false);
18+
const router = useRouter();
19+
const community = useCommunity();
20+
21+
const onSuccess = useCallback((pubTypeId: PubTypesId) => {
22+
setIsOpen(false);
23+
router.push(`/c/${community.slug}/types/${pubTypeId}/edit`);
24+
}, []);
25+
26+
return (
27+
<Dialog onOpenChange={setIsOpen} defaultOpen={false} open={isOpen}>
28+
<DialogOverlay />
29+
<DialogTrigger asChild>
30+
<Button
31+
variant="outline"
32+
size="sm"
33+
className={cn(
34+
"flex items-center gap-x-2 bg-emerald-500 py-4 text-white",
35+
className
36+
)}
37+
>
38+
<Plus size="12" className="mb-0.5" />
39+
Create Type
40+
</Button>
41+
</DialogTrigger>
42+
<DialogContent className="max-h-full min-w-[32rem] max-w-fit overflow-auto">
43+
<DialogTitle>Create Type</DialogTitle>
44+
45+
{isOpen && (
46+
<NewTypeForm mode="create" onSubmitSuccess={onSuccess}>
47+
<Footer submitText="Create" onCancel={() => setIsOpen(false)} />
48+
</NewTypeForm>
49+
)}
50+
</DialogContent>
51+
</Dialog>
52+
);
53+
};
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import type { Static } from "@sinclair/typebox";
2+
import type { ReactNode } from "react";
3+
import type { FieldValues, UseFormReturn } from "react-hook-form";
4+
5+
import { useCallback, useMemo } from "react";
6+
import { typeboxResolver } from "@hookform/resolvers/typebox";
7+
import { Type } from "@sinclair/typebox";
8+
import { useForm } from "react-hook-form";
9+
import { IdString } from "schemas/utils";
10+
11+
import type { PubFieldsId, PubTypesId } from "db/public";
12+
import type { PubFieldContext } from "ui/pubFields";
13+
import {
14+
Form,
15+
FormControl,
16+
FormDescription,
17+
FormField,
18+
FormItem,
19+
FormLabel,
20+
FormMessage,
21+
} from "ui/form";
22+
import { Input } from "ui/input";
23+
import { MultiSelect } from "ui/multi-select";
24+
import { usePubFieldContext } from "ui/pubFields";
25+
import { toast } from "ui/use-toast";
26+
27+
import { useCommunity } from "~/app/components/providers/CommunityProvider";
28+
import { didSucceed, useServerAction } from "~/lib/serverActions";
29+
import { updatePubType } from "./[pubTypeId]/edit/actions";
30+
import { createPubType } from "./actions";
31+
32+
const baseSchema = Type.Object({
33+
name: Type.String({
34+
minLength: 2,
35+
}),
36+
description: Type.Optional(Type.String()),
37+
38+
fields: Type.Array(IdString<PubFieldsId>(), {
39+
minItems: 1,
40+
}),
41+
});
42+
43+
type FormValues = Static<typeof baseSchema>;
44+
// type FormValues = Static<typeof createSchema>;
45+
46+
const DEFAULT_VALUES = {
47+
name: "",
48+
description: "",
49+
};
50+
51+
type FormType = UseFormReturn<FormValues, any, FieldValues>;
52+
53+
export const NewTypeForm = ({
54+
onSubmitSuccess,
55+
children,
56+
...props
57+
}: {
58+
onSubmitSuccess: (pubTypeId: PubTypesId) => void;
59+
children: ReactNode;
60+
} & (
61+
| {
62+
mode: "create";
63+
pubTypeId?: never;
64+
}
65+
| {
66+
mode: "edit";
67+
pubTypeId: PubTypesId;
68+
name: string;
69+
description?: string | null;
70+
}
71+
)) => {
72+
const runCreatePubType = useServerAction(createPubType);
73+
const runUpdatePubType = useServerAction(updatePubType);
74+
const pubFields = usePubFieldContext();
75+
const community = useCommunity();
76+
77+
const handleSubmit = useCallback(async (values: FormValues) => {
78+
if (props.mode === "edit") {
79+
const result = await runUpdatePubType({
80+
pubTypeId: props.pubTypeId,
81+
name: values.name,
82+
description: values.description,
83+
fields: [],
84+
});
85+
if (result && didSucceed(result)) {
86+
toast({ title: `Type ${values.name} updated` });
87+
onSubmitSuccess(props.pubTypeId);
88+
return;
89+
}
90+
91+
form.setError("root", { message: result.error });
92+
return;
93+
}
94+
95+
const result = await runCreatePubType(
96+
values.name,
97+
community.id,
98+
values.description,
99+
values.fields
100+
);
101+
if (result && didSucceed(result)) {
102+
toast({ title: `Type ${values.name} created` });
103+
onSubmitSuccess(result.data.id);
104+
return;
105+
}
106+
107+
form.setError("root", { message: result.error });
108+
}, []);
109+
110+
const resolver = useMemo(() => {
111+
if (props.mode === "create") {
112+
return typeboxResolver(Type.Required(baseSchema));
113+
}
114+
115+
return typeboxResolver(Type.Omit(baseSchema, ["fields"]));
116+
}, []);
117+
118+
const form = useForm<FormValues>({
119+
defaultValues:
120+
props.mode === "create"
121+
? DEFAULT_VALUES
122+
: {
123+
name: props.name,
124+
description: props.description ?? "",
125+
},
126+
resolver,
127+
});
128+
129+
return (
130+
<Form {...form}>
131+
<form onSubmit={form.handleSubmit(handleSubmit)}>
132+
<div className="mb-4 flex flex-col gap-6">
133+
<FormField
134+
control={form.control}
135+
name="name"
136+
render={({ field }) => (
137+
<FormItem>
138+
<FormLabel>Type Name</FormLabel>
139+
<FormControl>
140+
<Input placeholder="Name" {...field} />
141+
</FormControl>
142+
<FormMessage />
143+
</FormItem>
144+
)}
145+
/>
146+
<FormField
147+
control={form.control}
148+
name="description"
149+
render={({ field }) => (
150+
<FormItem>
151+
<FormLabel>Description</FormLabel>
152+
<FormDescription>
153+
Optional. A brief description of the type.
154+
</FormDescription>
155+
<FormControl>
156+
<Input placeholder="Description" {...field} />
157+
</FormControl>
158+
<FormMessage />
159+
</FormItem>
160+
)}
161+
/>
162+
{props.mode === "create" && <FieldSelector pubFields={pubFields} form={form} />}
163+
{form.formState.errors.root && (
164+
<div className="text-sm text-red-500">
165+
{form.formState.errors.root.message}
166+
</div>
167+
)}
168+
</div>
169+
{children}
170+
</form>
171+
</Form>
172+
);
173+
};
174+
175+
export const FieldSelector = ({
176+
pubFields,
177+
form,
178+
}: {
179+
pubFields: PubFieldContext;
180+
form: FormType;
181+
}) => {
182+
return (
183+
<FormField
184+
control={form.control}
185+
name="fields"
186+
render={({ field }) => (
187+
<FormItem>
188+
<FormLabel>Fields</FormLabel>
189+
<FormDescription>
190+
Select the fields that will be included in the type (minimum of 1).
191+
</FormDescription>
192+
<MultiSelect
193+
onValueChange={field.onChange}
194+
defaultValue={field.value}
195+
options={Object.values(pubFields).map((field) => ({
196+
label: field.name,
197+
value: field.id,
198+
}))}
199+
/>
200+
<FormMessage />
201+
</FormItem>
202+
)}
203+
/>
204+
);
205+
};

core/app/c/[communitySlug]/types/RemoveFieldButton.tsx

Lines changed: 0 additions & 69 deletions
This file was deleted.

0 commit comments

Comments
 (0)