Skip to content

Commit 1cb7df9

Browse files
Merge branch 'main' into feature/remove_locale_param
2 parents 670f0e8 + c1a629a commit 1cb7df9

111 files changed

Lines changed: 3794 additions & 1351 deletions

File tree

Some content is hidden

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

app/(gcforms)/(app administration)/admin/(with nav)/accounts/[id]/manage-forms/actions.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { TemplateHasUnprocessedSubmissions } from "@lib/templates/internal/error
66
import { revalidatePath } from "next/cache";
77
import { AuthenticatedAction } from "@lib/actions";
88
import { getTemplateWithAssignedUsers } from "@lib/templates/queries/getTemplateWithAssignedUsers";
9-
import { sendArchivedFormNotifications } from "@lib/notifications";
9+
import { sendArchivedFormNotifications } from "@lib/formEmailOrchestration";
1010

1111
// Public facing functions - they can be used by anyone who finds the associated server action identifer
1212

@@ -18,13 +18,14 @@ export const deleteForm = AuthenticatedAction(async (session, id: string) => {
1818
}
1919

2020
await deleteTemplate(id);
21-
await sendArchivedFormNotifications(
22-
session,
23-
id,
24-
template.formRecord.form.titleEn,
25-
template.formRecord.form.titleFr,
26-
template.users
27-
);
21+
22+
sendArchivedFormNotifications(session.user.email, {
23+
title: {
24+
en: template.formRecord.form.titleEn,
25+
fr: template.formRecord.form.titleFr,
26+
},
27+
ownersEmailAddresses: template.users.map((u) => u.email),
28+
});
2829

2930
revalidatePath("app/[locale]/(app administration)/admin/(with nav)/accounts/[id]/manage-forms");
3031
} catch (error) {

app/(gcforms)/[locale]/(form administration)/form-builder/[id]/components/LeftNavigation.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,14 @@ export const LeftNavigation = ({ id }: { id: string }) => {
3434
t,
3535
i18n: { language },
3636
} = useTranslation("form-builder");
37-
const { isPublished, id: storeId } = useTemplateStore((s) => ({
37+
const {
38+
isPublished,
39+
id: storeId,
40+
currentDraftVersionId,
41+
} = useTemplateStore((s) => ({
3842
isPublished: s.isPublished,
3943
id: s.id,
44+
currentDraftVersionId: s.currentDraftVersionId,
4045
}));
4146

4247
if (storeId && storeId !== id) {
@@ -48,7 +53,7 @@ export const LeftNavigation = ({ id }: { id: string }) => {
4853
return (
4954
<nav aria-label={t("navLabelFormBuilder")}>
5055
<ul className="m-0 list-none p-0">
51-
{!isPublished && (
56+
{(!isPublished || currentDraftVersionId) && (
5257
<li>
5358
<LeftNav
5459
testid="edit"
@@ -82,7 +87,7 @@ export const LeftNavigation = ({ id }: { id: string }) => {
8287
<NavSettingsIcon />
8388
</LeftNav>
8489
</li>
85-
{!isPublished && (
90+
{(!isPublished || currentDraftVersionId) && (
8691
<li>
8792
<LeftNav
8893
testid="publish"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
import React, { useState } from "react";
3+
import { useTranslation } from "@i18n/client";
4+
5+
export const ConfirmationAgreement = ({
6+
handleAgreement,
7+
}: {
8+
handleAgreement: (value: string) => void;
9+
}) => {
10+
const { t } = useTranslation("form-builder");
11+
const [agreeValue, setAgreeValue] = useState("");
12+
13+
return (
14+
<div className="mb-4">
15+
<label className="mb-2 block font-bold" id="enterAgreeInput">
16+
{t("confirm.createDraft.label")}{" "}
17+
<span className="text-gcds-red-500">{t("confirm.createDraft.required")}</span>
18+
</label>
19+
<div>
20+
<input
21+
type="text"
22+
id="confirmation-agree"
23+
className="gc-input-text mb-1 w-4/5"
24+
value={agreeValue}
25+
placeholder={t("confirm.createDraft.placeholder")}
26+
onChange={(e) => {
27+
setAgreeValue(e.target.value);
28+
handleAgreement((e.target.value || "").toUpperCase().trim());
29+
}}
30+
aria-labelledby="enterAgreeInput"
31+
/>
32+
</div>
33+
</div>
34+
);
35+
};
36+
37+
export default ConfirmationAgreement;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"use client";
2+
import React, { useCallback, useEffect, useState } from "react";
3+
import { useRouter } from "next/navigation";
4+
import { Dialog, useDialogRef } from "@formBuilder/components/shared/Dialog";
5+
import { Button } from "@clientComponents/globals";
6+
import { EventKeys, useCustomEvent } from "@lib/hooks/useCustomEvent";
7+
import ConfirmationAgreement from "./ConfirmationAgreement";
8+
import { toast } from "@formBuilder/components/shared/Toast";
9+
import { useTranslation } from "@i18n/client";
10+
import { createDraftVersion } from "./actions";
11+
12+
type OpenDetail = { id: string };
13+
14+
export const CreateDraftConfirmDialog = () => {
15+
const dialog = useDialogRef();
16+
const { Event } = useCustomEvent();
17+
const { t, i18n } = useTranslation("form-builder");
18+
const router = useRouter();
19+
20+
const [isOpen, setIsOpen] = useState(false);
21+
const [formId, setFormId] = useState<string>("");
22+
const [agreed, setAgreed] = useState(false);
23+
const [isSubmitting, setIsSubmitting] = useState(false);
24+
25+
const handleOpen = useCallback((detail: OpenDetail) => {
26+
if (detail && detail.id) {
27+
setFormId(detail.id);
28+
setAgreed(false);
29+
setIsOpen(true);
30+
}
31+
}, []);
32+
33+
useEffect(() => {
34+
Event.on(EventKeys.openCreateDraftConfirmDialog, handleOpen);
35+
return () => {
36+
Event.off(EventKeys.openCreateDraftConfirmDialog, handleOpen);
37+
};
38+
}, [Event, handleOpen]);
39+
40+
const handleAgreement = (value: string) => {
41+
if (value === "AGREE" || value === "ACCEPTE") setAgreed(true);
42+
else setAgreed(false);
43+
};
44+
45+
const handleClose = () => {
46+
dialog.current?.close();
47+
setIsOpen(false);
48+
setAgreed(false);
49+
};
50+
51+
const handleContinue = async () => {
52+
setIsSubmitting(true);
53+
try {
54+
const res = await createDraftVersion({ id: formId });
55+
if (res?.error || !res?.formRecord) {
56+
toast.error("Could not create draft version");
57+
setIsSubmitting(false);
58+
return;
59+
}
60+
// Navigate to edit page for the draft
61+
const lang = i18n.language || "en";
62+
router.push(`/${lang}/form-builder/${res.formRecord.id}/edit`);
63+
} catch (e) {
64+
toast.error("Could not create draft version");
65+
setIsSubmitting(false);
66+
}
67+
};
68+
69+
const actions = (
70+
<>
71+
<Button theme="secondary" onClick={handleClose} disabled={isSubmitting}>
72+
{t("actions.cancel")}
73+
</Button>
74+
<Button
75+
theme="primary"
76+
className="ml-4"
77+
onClick={handleContinue}
78+
disabled={!agreed || isSubmitting}
79+
>
80+
{isSubmitting ? t("actions.continuing") : t("actions.continue")}
81+
</Button>
82+
</>
83+
);
84+
85+
return (
86+
<>
87+
{isOpen && (
88+
<Dialog
89+
handleClose={handleClose}
90+
dialogRef={dialog}
91+
actions={actions}
92+
title={t("confirm.createDraft.title")}
93+
>
94+
<div className="p-4">
95+
<p className="mb-4">{t("confirm.createDraft.description")}</p>
96+
<p className="text-warning mb-4 font-medium">
97+
{t("confirm.createDraft.responseWarning")}
98+
</p>
99+
<p className="mb-4">{t("confirm.createDraft.liveForm")}</p>
100+
<p className="mb-4 font-semibold">{t("confirm.createDraft.prompt")}</p>
101+
<ConfirmationAgreement handleAgreement={handleAgreement} />
102+
</div>
103+
</Dialog>
104+
)}
105+
</>
106+
);
107+
};
108+
109+
export default CreateDraftConfirmDialog;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"use server";
2+
3+
import { revalidatePath } from "next/cache";
4+
5+
import { redirect } from "next/navigation";
6+
7+
import { createDraftVersionForTemplate } from "@lib/templates/versioning/mutations/createDraftForTemplate";
8+
9+
import { AuthenticatedAction } from "@lib/actions";
10+
import { type FormRecord } from "@lib/types";
11+
12+
export const createDraftVersion = AuthenticatedAction(
13+
async (
14+
_,
15+
{
16+
id: formID,
17+
redirectAfter,
18+
}: {
19+
id: string;
20+
redirectAfter?: string;
21+
}
22+
): Promise<{
23+
formRecord: FormRecord | null;
24+
error?: string;
25+
}> => {
26+
let hasError;
27+
let response: FormRecord | null = null;
28+
29+
try {
30+
response = await createDraftVersionForTemplate(formID);
31+
32+
if (!response) {
33+
throw new Error(`Unable to create a draft version for ${formID}`);
34+
}
35+
36+
revalidatePath(`/form-builder/${formID}`, "layout");
37+
revalidatePath(`/form-builder/${formID}/published`, "page");
38+
revalidatePath(`/form-builder/${formID}/publish`, "page");
39+
} catch (error) {
40+
hasError = error;
41+
}
42+
43+
if (!hasError && redirectAfter) {
44+
if (!redirectAfter.startsWith("/") || redirectAfter.startsWith("//")) {
45+
return { formRecord: response, error: "Invalid redirect path" };
46+
}
47+
redirect(redirectAfter);
48+
}
49+
50+
return { formRecord: response, error: hasError ? (hasError as Error).message : undefined };
51+
}
52+
);

app/(gcforms)/[locale]/(form administration)/form-builder/[id]/edit/components/EditWithGroups.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ import { SkipLinkReusable } from "@clientComponents/globals/SkipLinkReusable";
2525
import { AddPageButton } from "./AddPageButton";
2626
import { AddBranchingButton } from "./AddBranchingButton";
2727

28-
export const EditWithGroups = ({ id, locale }: { id: string; locale: string }) => {
28+
export const EditWithGroups = ({
29+
id,
30+
locale,
31+
hasDraft,
32+
}: {
33+
id: string;
34+
locale: string;
35+
hasDraft: boolean;
36+
}) => {
2937
const { t } = useTranslation("form-builder");
3038
const {
3139
title,
@@ -35,7 +43,6 @@ export const EditWithGroups = ({ id, locale }: { id: string; locale: string }) =
3543
getLocalizationAttribute,
3644
isPublished,
3745
getName,
38-
currentDraftVersionId,
3946
} = useTemplateStore((s) => ({
4047
title:
4148
s.form[s.localizeField(LocalizedFormProperties.TITLE, s.translationLanguagePriority)] ?? "",
@@ -44,7 +51,6 @@ export const EditWithGroups = ({ id, locale }: { id: string; locale: string }) =
4451
translationLanguagePriority: s.translationLanguagePriority,
4552
getLocalizationAttribute: s.getLocalizationAttribute,
4653
isPublished: s.isPublished,
47-
currentDraftVersionId: s.currentDraftVersionId,
4854
getName: s.getName,
4955
}));
5056

@@ -75,11 +81,11 @@ export const EditWithGroups = ({ id, locale }: { id: string; locale: string }) =
7581
}, [title]);
7682

7783
useEffect(() => {
78-
if (isPublished && !currentDraftVersionId) {
84+
if (isPublished && !hasDraft) {
7985
router.replace(`/${locale}/form-builder/${id}/published`);
8086
return;
8187
}
82-
}, [router, isPublished, id, locale, currentDraftVersionId]);
88+
}, [router, isPublished, id, locale, hasDraft]);
8389

8490
const _debounced = debounce(
8591
useCallback(

app/(gcforms)/[locale]/(form administration)/form-builder/[id]/edit/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EditWithGroups } from "./components/EditWithGroups";
44
import { DynamicRowDialog } from "@formBuilder/components/dialogs/DynamicRowDialog/DynamicRowDialog";
55
import { MoreDialog } from "../components/dialogs/MoreDialog/MoreDialog";
66
import { RulesDialog } from "../components/dialogs/RulesDialog/RulesDialog";
7+
import { getTemplateVersionState } from "@lib/templates/versioning/queries/getTemplateVersionState";
78

89
export async function generateMetadata(props: {
910
params: Promise<{ locale: string }>;
@@ -23,9 +24,15 @@ export default async function Page(props: { params: Promise<{ id: string; locale
2324

2425
const { id, locale } = params;
2526

27+
const templateVersionState = await getTemplateVersionState(id);
28+
2629
return (
2730
<>
28-
<EditWithGroups id={id} locale={locale} />
31+
<EditWithGroups
32+
id={id}
33+
hasDraft={!!templateVersionState?.currentDraftVersionId}
34+
locale={locale}
35+
/>
2936
<DynamicRowDialog />
3037
<MoreDialog />
3138
<RulesDialog />

app/(gcforms)/[locale]/(form administration)/form-builder/[id]/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export default async function Layout(props: {
110110
>
111111
<GroupStoreProvider>
112112
<ManageFormAccessDialogContainer formId={id} />
113+
113114
<div className="h-full">
114115
<div className="flex min-h-screen flex-col">
115116
<Header

app/(gcforms)/[locale]/(form administration)/form-builder/[id]/preview/ClientContainer.tsx

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

app/(gcforms)/[locale]/(form administration)/form-builder/[id]/preview/PreviewFormWrapper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const PreviewFormWrapper = ({
2727
setSent: React.Dispatch<React.SetStateAction<string | null | undefined>>;
2828
}) => {
2929
const { status } = useSession();
30-
const { saveSessionProgress, currentGroup } = useGCFormsContext();
30+
const { saveSessionProgress, currentGroup, matchedIds, getGroupHistory } = useGCFormsContext();
3131

3232
const { translationLanguagePriority, getLocalizationAttribute } = useTemplateStore((s) => ({
3333
translationLanguagePriority: s.translationLanguagePriority,
@@ -48,6 +48,8 @@ export const PreviewFormWrapper = ({
4848
language={translationLanguagePriority}
4949
t={translatedT}
5050
onSuccess={setSent}
51+
matchedIds={matchedIds}
52+
getGroupHistory={getGroupHistory}
5153
renderSubmit={({ validateForm }) => {
5254
return (
5355
<div id="PreviewSubmitButton">

0 commit comments

Comments
 (0)