Skip to content

Commit 13ee081

Browse files
committed
Add form switcher
1 parent 4dea6b3 commit 13ee081

File tree

11 files changed

+306
-31
lines changed

11 files changed

+306
-31
lines changed

core/app/c/[communitySlug]/members/page.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getPageLoginData } from "~/lib/authentication/loginData";
1010
import { userCan } from "~/lib/authorization/capabilities";
1111
import { firstRoleIsHigher } from "~/lib/authorization/rolesRanking";
1212
import { findCommunityBySlug } from "~/lib/server/community";
13-
import { getMembershipForms } from "~/lib/server/form";
13+
import { getSimpleForms } from "~/lib/server/form";
1414
import { selectAllCommunityMemberships } from "~/lib/server/member";
1515
import { addMember, createUserWithCommunityMembership } from "./actions";
1616
import { MemberTable } from "./MemberTable";
@@ -54,7 +54,7 @@ export default async function Page(props: {
5454
const page = parseInt(searchParams.page ?? "1", 10);
5555
const [members, availableForms] = await Promise.all([
5656
selectAllCommunityMemberships({ communityId: community.id }).execute(),
57-
getMembershipForms(),
57+
getSimpleForms(),
5858
]);
5959

6060
if (!members.length && page !== 1) {

core/app/c/[communitySlug]/pubs/[pubId]/edit/page.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,12 @@ export default async function Page(props: {
113113
return null;
114114
}
115115

116-
const formId = `edit-pub-${pub.id}`;
116+
const htmlFormId = `edit-pub-${pub.id}`;
117117

118118
return (
119119
<ContentLayout
120120
left={
121-
<Button form={formId} type="submit">
121+
<Button form={htmlFormId} type="submit">
122122
Save
123123
</Button>
124124
}
@@ -135,7 +135,7 @@ export default async function Page(props: {
135135
<PubEditor
136136
searchParams={searchParams}
137137
pubId={pub.id}
138-
formId={formId}
138+
htmlFormId={htmlFormId}
139139
communityId={community.id}
140140
/>
141141
</div>

core/app/c/[communitySlug]/pubs/[pubId]/page.tsx

+16-10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Assign from "~/app/c/[communitySlug]/stages/components/Assign";
1212
import Move from "~/app/c/[communitySlug]/stages/components/Move";
1313
import { MembersList } from "~/app/components//Memberships/MembersList";
1414
import { PubsRunActionDropDownMenu } from "~/app/components/ActionUI/PubsRunActionDropDownMenu";
15+
import { FormSwitcher } from "~/app/components/FormSwitcher/FormSwitcher";
1516
import { AddMemberDialog } from "~/app/components/Memberships/AddMemberDialog";
1617
import { CreatePubButton } from "~/app/components/pubs/CreatePubButton";
1718
import { RemovePubButton } from "~/app/components/pubs/RemovePubButton";
@@ -23,7 +24,7 @@ import { getPubByForm, getPubTitle } from "~/lib/pubs";
2324
import { getPubsWithRelatedValues, pubValuesByVal } from "~/lib/server";
2425
import { autoCache } from "~/lib/server/cache/autoCache";
2526
import { findCommunityBySlug } from "~/lib/server/community";
26-
import { getForm, getMembershipForms } from "~/lib/server/form";
27+
import { getForm, getSimpleForms } from "~/lib/server/form";
2728
import { selectAllCommunityMemberships } from "~/lib/server/member";
2829
import { getStages } from "~/lib/server/stages";
2930
import {
@@ -69,7 +70,7 @@ export default async function Page(props: {
6970
params: Promise<{ pubId: PubsId; communitySlug: string }>;
7071
searchParams: Promise<Record<string, string>>;
7172
}) {
72-
const searchParams = await props.searchParams;
73+
const { form: formSlug, ...searchParams } = await props.searchParams;
7374
const params = await props.params;
7475
const { pubId, communitySlug } = params;
7576

@@ -138,24 +139,26 @@ export default async function Page(props: {
138139
}
139140

140141
const actionsPromise = pub.stage ? getStageActions(pub.stage.id).execute() : null;
141-
142+
const getFormProps = formSlug
143+
? { communityId: community.id, slug: formSlug }
144+
: {
145+
communityId: community.id,
146+
pubTypeId: pub.pubType.id,
147+
};
142148
const [actions, communityMembers, communityStages, form, withExtraPubValues, availableForms] =
143149
await Promise.all([
144150
actionsPromise,
145151
communityMembersPromise,
146152
communityStagesPromise,
147-
getForm({
148-
communityId: community.id,
149-
pubTypeId: pub.pubType.id,
150-
}).executeTakeFirstOrThrow(
153+
getForm(getFormProps).executeTakeFirstOrThrow(
151154
() => new Error(`Could not find a form for pubtype ${pub.pubType.name}`)
152155
),
153156
userCan(
154157
Capabilities.seeExtraPubValues,
155158
{ type: MembershipType.pub, pubId: pub.id },
156159
user.id
157160
),
158-
getMembershipForms(pub.pubType.id),
161+
getSimpleForms(pub.pubType.id),
159162
]);
160163

161164
const pubTypeHasRelatedPubs = pub.pubType.fields.some((field) => field.isRelation);
@@ -171,8 +174,11 @@ export default async function Page(props: {
171174
<div className="flex flex-col space-y-4">
172175
<div className="mb-8 flex items-center justify-between">
173176
<div>
174-
<div className="text-lg font-semibold text-muted-foreground">
175-
{pub.pubType.name}
177+
<div className="flex items-center gap-2">
178+
<span className="text-lg font-semibold text-muted-foreground">
179+
{pub.pubType.name}
180+
</span>
181+
<FormSwitcher defaultFormSlug={formSlug} forms={availableForms} />
176182
</div>
177183
<h1 className="mb-2 text-xl font-bold">{getPubTitle(pub)} </h1>
178184
</div>

core/app/c/[communitySlug]/pubs/create/page.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { Metadata } from "next";
22

33
import { notFound, redirect } from "next/navigation";
44

5-
import type { CommunitiesId } from "db/public";
65
import { Capabilities, MembershipType } from "db/public";
76
import { Button } from "ui/button";
87

@@ -57,12 +56,12 @@ export default async function Page(props: {
5756
redirect(`/c/${communitySlug}/unauthorized`);
5857
}
5958

60-
const formId = `create-pub`;
59+
const htmlFormId = `create-pub`;
6160

6261
return (
6362
<ContentLayout
6463
left={
65-
<Button form={formId} type="submit">
64+
<Button form={htmlFormId} type="submit">
6665
Save
6766
</Button>
6867
}
@@ -74,7 +73,7 @@ export default async function Page(props: {
7473
<PubEditor
7574
searchParams={searchParams}
7675
communityId={community.id}
77-
formId={formId}
76+
htmlFormId={htmlFormId}
7877
// PubEditor checks for the existence of the stageId prop
7978
{...(searchParams["stageId"] ? { stageId: searchParams["stageId"] } : {})}
8079
/>

core/app/c/[communitySlug]/stages/manage/components/panel/StagePanelMembers.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { AddMemberDialog } from "~/app/components/Memberships/AddMemberDialog";
1212
import { SkeletonCard } from "~/app/components/skeletons/SkeletonCard";
1313
import { userCan } from "~/lib/authorization/capabilities";
1414
import { getStageMembers } from "~/lib/db/queries";
15-
import { getMembershipForms } from "~/lib/server/form";
15+
import { getSimpleForms } from "~/lib/server/form";
1616
import {
1717
addStageMember,
1818
addUserWithStageMembership,
@@ -29,7 +29,7 @@ const StagePanelMembersInner = async ({ stageId, user }: PropsInner) => {
2929
const [members, canManage, availableForms] = await Promise.all([
3030
getStageMembers(stageId).execute(),
3131
userCan(Capabilities.removeStageMember, { type: MembershipType.stage, stageId }, user.id),
32-
getMembershipForms(),
32+
getSimpleForms(),
3333
]);
3434

3535
return (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
5+
6+
import { Button } from "ui/button";
7+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "ui/select";
8+
9+
import type { SimpleForm } from "~/lib/server/form";
10+
11+
export const formSwitcherUrlParam = "form";
12+
13+
export const FormSwitcher = ({
14+
forms,
15+
defaultFormSlug,
16+
}: {
17+
forms: SimpleForm[];
18+
defaultFormSlug?: string;
19+
}) => {
20+
const router = useRouter();
21+
const pathname = usePathname();
22+
const params = useSearchParams();
23+
24+
const [selectedFormSlug, setSelectedFormSlug] = useState(defaultFormSlug ?? forms[0].slug);
25+
if (!forms.length) {
26+
return null;
27+
}
28+
29+
const switchForms = () => {
30+
if (!selectedFormSlug) {
31+
return;
32+
}
33+
const newParams = new URLSearchParams(params);
34+
newParams.set(formSwitcherUrlParam, selectedFormSlug);
35+
router.push(`${pathname}?${newParams.toString()}`);
36+
};
37+
38+
return (
39+
<div className="flex items-center gap-2">
40+
<Select
41+
onValueChange={(slug: string) => {
42+
setSelectedFormSlug(slug);
43+
}}
44+
defaultValue={selectedFormSlug}
45+
>
46+
<SelectTrigger className="">
47+
<SelectValue />
48+
</SelectTrigger>
49+
<SelectContent>
50+
{forms.map((form) => (
51+
<SelectItem key={form.id} value={form.slug}>
52+
{form.name}
53+
</SelectItem>
54+
))}
55+
</SelectContent>
56+
</Select>
57+
<Button onClick={switchForms}>Switch forms</Button>
58+
</div>
59+
);
60+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use client";
2+
3+
import type { Dispatch, PropsWithChildren, SetStateAction } from "react";
4+
5+
import { createContext, useContext, useMemo, useState } from "react";
6+
7+
import type { Form } from "~/lib/server/form";
8+
9+
export type FormSwitcherContext = {
10+
selectedForm?: Form;
11+
setSelectedForm: Dispatch<SetStateAction<Form | undefined>>;
12+
};
13+
14+
const FormSwitcherContext = createContext<FormSwitcherContext>({
15+
setSelectedForm: () => {},
16+
});
17+
18+
type Props = PropsWithChildren<{
19+
defaultForm?: Form;
20+
}>;
21+
22+
export const FormSwitcherProvider = ({ defaultForm, children }: Props) => {
23+
const [selectedForm, setSelectedForm] = useState(defaultForm);
24+
25+
const value = useMemo(
26+
() => ({ selectedForm, setSelectedForm }),
27+
[selectedForm, setSelectedForm]
28+
);
29+
30+
return <FormSwitcherContext.Provider value={value}>{children}</FormSwitcherContext.Provider>;
31+
};
32+
33+
export const useFormSwitcherContext = () => useContext(FormSwitcherContext);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { skipToken } from "@tanstack/react-query";
5+
6+
import type { FormsId } from "db/public";
7+
import { Button } from "ui/button";
8+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "ui/select";
9+
import { toast } from "ui/use-toast";
10+
11+
import type { SimpleForm } from "~/lib/server/form";
12+
import { client } from "~/lib/api";
13+
import { useCommunity } from "../providers/CommunityProvider";
14+
import { useFormSwitcherContext } from "./FormSwitcherContext";
15+
16+
const FormSwitcher = ({ forms }: { forms: SimpleForm[] }) => {
17+
const community = useCommunity();
18+
const { selectedForm, setSelectedForm } = useFormSwitcherContext();
19+
const [selectedFormId, setSelectedFormId] = useState(selectedForm?.id);
20+
const { isLoading, refetch } = client.forms.get.useQuery({
21+
enabled: false,
22+
queryKey: ["getForm", selectedFormId, community.slug],
23+
queryData: selectedFormId
24+
? { params: { formId: selectedFormId, communitySlug: community.slug } }
25+
: skipToken,
26+
select(data) {
27+
if (data.body) {
28+
setSelectedForm(data.body);
29+
}
30+
},
31+
throwOnError(error, query) {
32+
toast({
33+
title: "Error",
34+
description: `Unable to switch forms`,
35+
variant: "destructive",
36+
});
37+
return false;
38+
},
39+
});
40+
41+
if (!forms.length) {
42+
return null;
43+
}
44+
45+
const switchForms = () => {
46+
refetch();
47+
};
48+
49+
return (
50+
<div>
51+
<Select
52+
onValueChange={(value: FormsId) => {
53+
setSelectedFormId(value);
54+
}}
55+
disabled={isLoading}
56+
>
57+
<SelectTrigger className="">
58+
<SelectValue placeholder="Form" />
59+
</SelectTrigger>
60+
<SelectContent>
61+
{forms.map((form) => (
62+
<SelectItem key={form.id} value={form.id}>
63+
{form.name}
64+
</SelectItem>
65+
))}
66+
</SelectContent>
67+
</Select>
68+
<Button onClick={switchForms}>Switch forms</Button>
69+
</div>
70+
);
71+
};

core/app/components/Memberships/MemberInviteForm.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export const MemberInviteForm = ({
262262
</FormItem>
263263
)}
264264
/>
265-
{isContributor && (
265+
{isContributor && !!availableForms.length && (
266266
<FormField
267267
control={form.control}
268268
name="forms"

core/app/components/pubs/PubEditor/PubEditor.tsx

+10-6
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ const getRelatedPubData = async ({
104104

105105
export type PubEditorProps = {
106106
searchParams: { relatedPubId?: PubsId; slug?: string; pubTypeId?: PubTypesId };
107-
formId?: string;
107+
htmlFormId?: string;
108+
formSlug?: string;
108109
} & (
109110
| {
110111
pubId: PubsId;
@@ -214,10 +215,13 @@ export async function PubEditor(props: PubEditorProps) {
214215
);
215216
}
216217

217-
const form = await getForm({
218-
communityId: community.id,
219-
pubTypeId: pubType.id,
220-
}).executeTakeFirstOrThrow(
218+
const getFormProps = props.formSlug
219+
? { communityId: community.id, slug: props.formSlug }
220+
: {
221+
communityId: community.id,
222+
pubTypeId: pubType.id,
223+
};
224+
const form = await getForm(getFormProps).executeTakeFirstOrThrow(
221225
() => new Error(`Could not find a form for pubtype ${pubType.name}`)
222226
);
223227

@@ -294,7 +298,7 @@ export async function PubEditor(props: PubEditorProps) {
294298
isUpdating={isUpdating}
295299
withAutoSave={false}
296300
withButtonElements
297-
htmlFormId={props.formId}
301+
htmlFormId={props.htmlFormId}
298302
stageId={currentStageId}
299303
relatedPub={
300304
relatedPubId && relatedFieldSlug

0 commit comments

Comments
 (0)