Skip to content

Commit

Permalink
feat: allow users to change the default new submission email recipient (
Browse files Browse the repository at this point in the history
  • Loading branch information
AmoabaKelvin committed Jul 20, 2024
1 parent 84318ab commit e01677f
Show file tree
Hide file tree
Showing 9 changed files with 705 additions and 40 deletions.
108 changes: 105 additions & 3 deletions apps/web/src/app/(main)/form/[id]/form-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { toast } from 'sonner';
import { z } from 'zod';

import { type RouterOutputs } from '@formbase/api';
import { type UserInstance } from '@formbase/auth';
import { Button } from '@formbase/ui/primitives/button';
import {
Form,
Expand Down Expand Up @@ -47,17 +48,26 @@ const enableNotificationsSchema = z.object({
enableNotifications: z.boolean().default(true).optional(),
});

const defaultSubmissionEmailSchema = z.object({
defaultSubmissionEmail: z.string().email().optional(),
});

type FormNameSchema = z.infer<typeof formNameSchema>;
type EnableFormSubmissionsSchema = z.infer<typeof enableFormSubmissionsSchema>;
type _EnableSubmissionsRetentionSchema = z.infer<typeof enableRetentionSchema>;
type EnableFormNotificationsSchema = z.infer<typeof enableNotificationsSchema>;
type FormReturnUrlSchema = z.infer<typeof formReturnUrlSchema>;

type DefaultSubmissionEmailSchema = z.infer<
typeof defaultSubmissionEmailSchema
>;

type FormSettingsProps = {
form: RouterOutputs['form']['get'];
user: UserInstance | null;
};

export function FormSettings({ form }: FormSettingsProps) {
export function FormSettings({ form, user }: FormSettingsProps) {
const router = useRouter();

if (!form) {
Expand Down Expand Up @@ -85,6 +95,14 @@ export function FormSettings({ form }: FormSettingsProps) {
enableNotifications={form.enableEmailNotifications}
/>

{/* only show the if the user has enabled email notifications */}
{form.enableEmailNotifications && (
<FormDefaultSubmissionEmailRecipient
formId={form.id}
email={form.defaultSubmissionEmail ?? user?.email ?? ''}
/>
)}

<div className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<Label className="text-base">Delete Form</Label>
Expand Down Expand Up @@ -247,6 +265,90 @@ const ReturnUrlForm = ({
);
};

const FormDefaultSubmissionEmailRecipient = ({
formId,
email,
}: {
formId: string;
email: string;
}) => {
const router = useRouter();

const formDefaultSubmissionEmail = useForm<DefaultSubmissionEmailSchema>({
resolver: zodResolver(defaultSubmissionEmailSchema),
defaultValues: {
defaultSubmissionEmail: email,
},
});

const {
mutateAsync: updateFormDefaultSubmissionEmail,
isPending: isUpdatingFormDefaultSubmissionEmail,
} = api.form.update.useMutation();

async function handleFormDefaultSubmissionEmailSubmit(
data: DefaultSubmissionEmailSchema,
) {
try {
await updateFormDefaultSubmissionEmail({
id: formId,
defaultSubmissionEmail: data.defaultSubmissionEmail,
});

toast('Your form default submission email has been updated', {
icon: <FolderPen className="h-4 w-4" />,
});

router.refresh();
} catch {
toast('Failed to update form', {
description: 'Please try again later',
icon: <FolderX className="h-4 w-4" />,
});
}
}

return (
<Form {...formDefaultSubmissionEmail}>
<form
onSubmit={formDefaultSubmissionEmail.handleSubmit(
handleFormDefaultSubmissionEmailSubmit,
)}
>
<FormField
control={formDefaultSubmissionEmail.control}
name="defaultSubmissionEmail"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Default Submission Email
</FormLabel>
<FormDescription>
This is the email address that submission emails will be sent
to
</FormDescription>
</div>
<FormControl>
<div className="flex gap-2">
<Input className="w-[250px]" {...field} min={1} />
<Button
loading={isUpdatingFormDefaultSubmissionEmail}
type="submit"
variant="default"
>
Save
</Button>
</div>
</FormControl>
</FormItem>
)}
/>
</form>
</Form>
);
};

const EnableFormSubmissions = ({
formId,
enableSubmissions,
Expand Down Expand Up @@ -311,7 +413,7 @@ const EnableFormSubmissions = ({
</div>
<FormControl>
<Switch
checked={field.value}
checked={field.value ?? false}
onCheckedChange={async (isChecked) => {
field.onChange(isChecked);
await handleEnableSubmissionsRetentionSubmit({
Expand Down Expand Up @@ -391,7 +493,7 @@ const EnableFormNotifications = ({
</div>
<FormControl>
<Switch
checked={field.value}
checked={field.value ?? false}
onCheckedChange={async (isChecked) => {
field.onChange(isChecked);
await handleEnableSubmissionsNotifications({
Expand Down
26 changes: 12 additions & 14 deletions apps/web/src/app/(main)/form/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { type FormData } from '@formbase/db/schema';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@formbase/ui/primitives/tabs';
import { validateRequest } from "@formbase/auth";
import { FormData } from "@formbase/db/schema";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@formbase/ui/primitives/tabs";

import { CopyButton } from '~/components/copy-button';
import { api } from '~/lib/trpc/server';
import { CopyButton } from "~/components/copy-button";
import { api } from "~/lib/trpc/server";

import { EmptyFormState } from '../../dashboard/_components/empty-state';
import { ExportSubmissionsDropDownButton } from './export-submissions-button';
import { FormSettings } from './form-settings';
import { SubmissionsTable } from './submissions-table';
import { EmptyFormState } from "../../dashboard/_components/empty-state";
import { ExportSubmissionsDropDownButton } from "./export-submissions-button";
import { FormSettings } from "./form-settings";
import { SubmissionsTable } from "./submissions-table";

export default async function FormPage({ params }: { params: { id: string } }) {
const formId = params.id;
Expand All @@ -21,6 +17,8 @@ export default async function FormPage({ params }: { params: { id: string } }) {
api.formData.all({ formId }),
]);

const { user } = await validateRequest();

return (
<div className="space-y-6">
<div>
Expand Down Expand Up @@ -66,7 +64,7 @@ export default async function FormPage({ params }: { params: { id: string } }) {
{/* <TabsContent value="setup">Change your password here.</TabsContent> */}
{/* <TabsContent value="analytics">Look at your analytics here</TabsContent> */}
<TabsContent value="settings" className="my-6">
<FormSettings form={form} />
<FormSettings form={form} user={user} />
</TabsContent>
</Tabs>
</div>
Expand Down
17 changes: 8 additions & 9 deletions apps/web/src/app/api/s/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { userAgent } from 'next/server';
import { Form } from "@formbase/db/schema";
import { env } from "@formbase/env";
import { userAgent } from "next/server";

import { type Form } from '@formbase/db/schema';
import { env } from '@formbase/env';

import { sendMail } from '~/lib/email/mailer';
import { renderNewSubmissionEmail } from '~/lib/email/templates/new-submission';
import { api } from '~/lib/trpc/server';
import { assignFileOrImage, uploadFileFromBlob } from '~/lib/upload-file';
import { sendMail } from "~/lib/email/mailer";
import { renderNewSubmissionEmail } from "~/lib/email/templates/new-submission";
import { api } from "~/lib/trpc/server";
import { assignFileOrImage, uploadFileFromBlob } from "~/lib/upload-file";

type Json = string | number | boolean | null | { [key: string]: Json } | Json[];

Expand Down Expand Up @@ -68,7 +67,7 @@ async function handleEmailNotifications(
if (!user) throw new Error('User not found');

await sendMail({
to: user.email,
to: form.defaultSubmissionEmail ?? user.email,
subject: `New Submission for "${form.title}"`,
body: renderNewSubmissionEmail({
formTitle: form.title,
Expand Down
16 changes: 10 additions & 6 deletions packages/api/routers/form.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { z } from 'zod';
import { drizzlePrimitives } from "@formbase/db";
import { formDatas, forms, onboardingForms } from "@formbase/db/schema";
import { generateId } from "@formbase/utils/generate-id";
import { z } from "zod";

import { drizzlePrimitives } from '@formbase/db';
import { formDatas, forms, onboardingForms } from '@formbase/db/schema';
import { generateId } from '@formbase/utils/generate-id';

import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";

const { and, count, eq } = drizzlePrimitives;

Expand Down Expand Up @@ -57,6 +56,7 @@ export const formRouter = createTRPCRouter({
)
.mutation(async ({ ctx, input }) => {
const id = generateId(15);
const userEmail = ctx.user.email;

await ctx.db.insert(forms).values({
id,
Expand All @@ -68,6 +68,7 @@ export const formRouter = createTRPCRouter({
keys: [''],
enableEmailNotifications: true,
enableSubmissions: true,
defaultSubmissionEmail: userEmail,
});

return { id };
Expand Down Expand Up @@ -114,6 +115,7 @@ export const formRouter = createTRPCRouter({
enableSubmissions: z.boolean().optional(),
enableEmailNotifications: z.boolean().optional(),
returnUrl: z.string().optional(),
defaultSubmissionEmail: z.string().optional(),
}),
)
.mutation(async ({ ctx, input }) => {
Expand All @@ -135,6 +137,8 @@ export const formRouter = createTRPCRouter({
enableEmailNotifications:
input.enableEmailNotifications ?? form.enableEmailNotifications,
returnUrl: input.returnUrl ?? form.returnUrl,
defaultSubmissionEmail:
input.defaultSubmissionEmail ?? form.defaultSubmissionEmail,
})
.where(eq(forms.id, input.id));
}),
Expand Down
15 changes: 10 additions & 5 deletions packages/auth/lucia.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { User } from '@formbase/db/schema';

import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import { Lucia } from 'lucia';
import { db } from "@formbase/db";
import { sessions, users } from "@formbase/db/schema";
import { env } from "@formbase/env";
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { Lucia } from "lucia";

import { db } from '@formbase/db';
import { sessions, users } from '@formbase/db/schema';
import { env } from '@formbase/env';
import type { User as LuciaUser } from 'lucia';

const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);

Expand Down Expand Up @@ -36,3 +37,7 @@ declare module 'lucia' {
DatabaseUserAttributes: DatabaseUserAttributes;
}
}


// export lucia user type
export type UserInstance = LuciaUser;
1 change: 1 addition & 0 deletions packages/db/drizzle/0001_military_rictor.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "forms" ADD COLUMN "default_submission_email" varchar(255);
Loading

0 comments on commit e01677f

Please sign in to comment.