diff --git a/apps/web/package.json b/apps/web/package.json index 06a1f9c..2659a52 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -28,12 +28,9 @@ "@trpc/client": "next", "@trpc/react-query": "next", "@trpc/server": "next", - "clsx": "^2.1.1", "date-fns": "^3.6.0", - "geist": "^1.3.0", "lucide-react": "^0.379.0", "minio": "^8.0.0", - "nanoid": "^5.0.7", "next": "14.2.3", "next-themes": "^0.3.0", "nodemailer": "^6.9.13", @@ -43,10 +40,7 @@ "server-only": "^0.0.1", "sonner": "^1.4.41", "superjson": "^2.2.1", - "tailwind-merge": "^2.3.0", - "tailwindcss-animate": "^1.0.7", "ts-pattern": "^5.1.2", - "uploadthing": "^6.10.4", "zod": "^3.23.8" }, "devDependencies": { diff --git a/apps/web/src/app/(landing)/layout.tsx b/apps/web/src/app/(landing)/layout.tsx index 5a6e332..50b097f 100644 --- a/apps/web/src/app/(landing)/layout.tsx +++ b/apps/web/src/app/(landing)/layout.tsx @@ -1,4 +1,5 @@ import { type ReactNode } from 'react'; +import { redirect } from 'next/navigation'; import { validateRequest } from '@formbase/auth'; @@ -8,6 +9,10 @@ import { SiteFooter } from './_components/site-footer'; async function LandingPageLayout({ children }: { children: ReactNode }) { const { user } = await validateRequest(); + if (user) { + redirect('/dashboard'); + } + return (
diff --git a/apps/web/src/app/(main)/dashboard/_components/new-form-dialog.tsx b/apps/web/src/app/(main)/dashboard/_components/new-form-dialog.tsx index 4b62236..4846cf9 100644 --- a/apps/web/src/app/(main)/dashboard/_components/new-form-dialog.tsx +++ b/apps/web/src/app/(main)/dashboard/_components/new-form-dialog.tsx @@ -4,6 +4,7 @@ import React, { useState, useTransition } from 'react'; import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; +import { InfoCircledIcon } from '@radix-ui/react-icons'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; @@ -20,13 +21,17 @@ import { import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@formbase/ui/primitives/form'; import { Input } from '@formbase/ui/primitives/input'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@formbase/ui/primitives/tooltip'; import { api } from '~/lib/trpc/react'; @@ -61,8 +66,7 @@ export function CreateFormDialog() { { title: data.name, description: data.description, - returningUrl: data.returnUrl, - keys: [], + returnUrl: data.returnUrl, }, { onSuccess: ({ id }) => { @@ -103,11 +107,8 @@ export function CreateFormDialog() { control={form.control} name="name" render={({ field }) => ( - + Name - - How you want to call your form? - @@ -120,11 +121,8 @@ export function CreateFormDialog() { control={form.control} name="description" render={({ field }) => ( - + Description - - Describe your form - @@ -137,11 +135,18 @@ export function CreateFormDialog() { control={form.control} name="returnUrl" render={({ field }) => ( - - Return URL - - Where should users be redirected after form submission? - + + + Return URL + + + + + + Where should users be redirected after form submission? + + + diff --git a/apps/web/src/app/(main)/form/[id]/form-settings.tsx b/apps/web/src/app/(main)/form/[id]/form-settings.tsx index d9b0838..55036ee 100644 --- a/apps/web/src/app/(main)/form/[id]/form-settings.tsx +++ b/apps/web/src/app/(main)/form/[id]/form-settings.tsx @@ -65,7 +65,7 @@ export function FormSettings({ form }: FormSettingsProps) { } const redirectToDashboard = () => { - refreshDashboardAfterDeletion(); + void refreshDashboardAfterDeletion(); router.push('/dashboard'); }; @@ -263,14 +263,14 @@ const EnableFormSubmissions = ({ }, }); - const { mutateAsync: updateForm, isPending: isUpdatingForm } = + const { mutateAsync: updateFormSubmissions, isPending: isUpdatingForm } = api.form.update.useMutation(); async function handleEnableSubmissionsRetentionSubmit( data: EnableFormSubmissionsSchema, ) { try { - await updateForm({ + await updateFormSubmissions({ id: formId, enableSubmissions: data.enableFormSubmissions, }); @@ -343,16 +343,16 @@ const EnableFormNotifications = ({ }, }); - const { mutateAsync: updateForm, isPending: isUpdatingForm } = + const { mutateAsync: updateFormNotifications, isPending: isUpdatingForm } = api.form.update.useMutation(); async function handleEnableSubmissionsNotifications( data: EnableFormNotificationsSchema, ) { try { - await updateForm({ + await updateFormNotifications({ id: formId, - enableNotifications: data.enableNotifications, + enableEmailNotifications: data.enableNotifications, }); toast( diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index fcfbace..adbe4c3 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -8,6 +8,7 @@ import type { Metadata, Viewport } from 'next'; import { Toaster } from 'sonner'; import { env } from '@formbase/env'; +import { TooltipProvider } from '@formbase/ui/primitives/tooltip'; import { cn } from '@formbase/ui/utils/cn'; import { ThemeProvider } from '~/components/theme-provider'; @@ -53,7 +54,9 @@ export default function RootLayout({ enableSystem disableTransitionOnChange > - {children} + + {children} + {env.UMAMI_TRACKING_ID && ( diff --git a/packages/api/routers/form.ts b/packages/api/routers/form.ts index 3110446..d859748 100644 --- a/packages/api/routers/form.ts +++ b/packages/api/routers/form.ts @@ -47,7 +47,13 @@ export const formRouter = createTRPCRouter({ ), create: protectedProcedure - .input(ZInsertFormSchema) + .input( + z.object({ + title: z.string(), + description: z.string().optional(), + returnUrl: z.string().optional(), + }), + ) .mutation(async ({ ctx, input }) => { const id = generateId(15); @@ -59,25 +65,43 @@ export const formRouter = createTRPCRouter({ updatedAt: new Date(), returnUrl: input.returnUrl ?? null, keys: [''], - enableEmailNotifications: input.enableEmailNotifications ?? true, - enableSubmissions: input.enableSubmissions ?? true, + enableEmailNotifications: true, + enableSubmissions: true, }); return { id }; }), update: protectedProcedure - .input(ZUpdateFormSchema) + .input( + z.object({ + id: z.string(), + title: z.string().optional(), + description: z.string().optional(), + enableSubmissions: z.boolean().optional(), + enableEmailNotifications: z.boolean().optional(), + returnUrl: z.string().optional(), + }), + ) .mutation(async ({ ctx, input }) => { + const form = await ctx.db.query.forms.findFirst({ + where: (table, { eq }) => eq(table.id, input.id), + }); + + if (!form) { + throw new Error('Form not found'); + } + await ctx.db .update(forms) .set({ - title: input.title, - description: input.description ?? null, + title: input.title ?? form.title, + description: input.description ?? form.description, updatedAt: new Date(), - enableSubmissions: input.enableSubmissions ?? true, - enableEmailNotifications: input.enableEmailNotifications ?? true, - returnUrl: input.returnUrl ?? null, + enableSubmissions: input.enableSubmissions ?? form.enableSubmissions, + enableEmailNotifications: + input.enableEmailNotifications ?? form.enableEmailNotifications, + returnUrl: input.returnUrl ?? form.returnUrl, }) .where(eq(forms.id, input.id)); }), diff --git a/packages/db/schema/forms.ts b/packages/db/schema/forms.ts index 1a61bcc..f87979b 100644 --- a/packages/db/schema/forms.ts +++ b/packages/db/schema/forms.ts @@ -1,7 +1,6 @@ import { type InferSelectModel } from 'drizzle-orm'; import { boolean, index, pgTable, text, timestamp } from 'drizzle-orm/pg-core'; import { createInsertSchema, createSelectSchema } from 'drizzle-zod'; -import { type z } from 'zod'; export const forms = pgTable( 'forms', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac8cf50..897bc66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,24 +122,15 @@ importers: '@trpc/server': specifier: next version: 11.0.0-rc.390 - clsx: - specifier: ^2.1.1 - version: 2.1.1 date-fns: specifier: ^3.6.0 version: 3.6.0 - geist: - specifier: ^1.3.0 - version: 1.3.0(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) lucide-react: specifier: ^0.379.0 version: 0.379.0(react@18.3.1) minio: specifier: ^8.0.0 version: 8.0.0 - nanoid: - specifier: ^5.0.7 - version: 5.0.7 next: specifier: 14.2.3 version: 14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -167,18 +158,9 @@ importers: superjson: specifier: ^2.2.1 version: 2.2.1 - tailwind-merge: - specifier: ^2.3.0 - version: 2.3.0 - tailwindcss-animate: - specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.3) ts-pattern: specifier: ^5.1.2 version: 5.1.2 - uploadthing: - specifier: ^6.10.4 - version: 6.12.0(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3) zod: specifier: ^3.23.8 version: 3.23.8 @@ -756,12 +738,6 @@ packages: '@braintree/sanitize-url@6.0.4': resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} - '@effect/schema@0.66.16': - resolution: {integrity: sha512-sT/k5NOgKslGPzs3DUaCFuM6g2JQoIIT8jpwEorAZooplPIMK2xIspr7ECz6pp6Dc7Wz/ppXGk7HVyGZQsIYEQ==} - peerDependencies: - effect: ^3.1.3 - fast-check: ^3.13.2 - '@emnapi/core@0.45.0': resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==} @@ -2746,12 +2722,6 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@uploadthing/mime-types@0.2.10': - resolution: {integrity: sha512-kz3F0oEgAyts25NAGXlUBCWh3mXonbSOQJFGFMawHuIgbUbnzXbe4w5WI+0XdneCbjNmikfWrdWrs8m/7HATfQ==} - - '@uploadthing/shared@6.7.5': - resolution: {integrity: sha512-BZXzvh6zGEt4ip//mxfXdRTNWYw9XJ6tommL6A1TEo2l8jvdNbUpPUwXnMVWBMwio2b48BO7D9V3siYIKMD4pg==} - '@zxing/text-encoding@0.9.0': resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} @@ -3107,10 +3077,6 @@ packages: config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - consola@3.2.3: - resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} - engines: {node: ^14.18.0 || >=16.10.0} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -3527,9 +3493,6 @@ packages: engines: {node: '>=14'} hasBin: true - effect@3.2.8: - resolution: {integrity: sha512-W9xHCXg+CKwvNb+/vEsfiBxSqyOEAyjo7mz1M7Kq5ZtxBUyefOsQxc3oGME7bTcxD4OozWcR27Sk4iYxHERoZg==} - electron-to-chromium@1.4.789: resolution: {integrity: sha512-0VbyiaXoT++Fi2vHGo2ThOeS6X3vgRCWrjPeO2FeIAWL6ItiSJ9BqlH8LfCXe3X1IdcG+S0iLoNaxQWhfZoGzQ==} @@ -3833,10 +3796,6 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fast-check@3.19.0: - resolution: {integrity: sha512-CO2JX/8/PT9bDGO1iXa5h5ey1skaKI1dvecERyhH4pp3PGjwd3KIjMAXEg79Ps9nclsdt4oPbfqiAnLU0EwrAQ==} - engines: {node: '>=8.0.0'} - fast-deep-equal@2.0.1: resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} @@ -3924,11 +3883,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - geist@1.3.0: - resolution: {integrity: sha512-IoGBfcqVEYB4bEwsfHd35jF4+X9LHRPYZymHL4YOltHSs9LJa24DYs1Z7rEMQ/lsEvaAIc61Y9aUxgcJaQ8lrg==} - peerDependencies: - next: '>=13.2.0 <15.0.0-0' - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5361,9 +5315,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.12.1: resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} engines: {node: '>=0.6'} @@ -5712,9 +5663,6 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - stream-chain@2.2.5: resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} @@ -6107,27 +6055,6 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - uploadthing@6.12.0: - resolution: {integrity: sha512-uoWG1riH6z2IHCcbMo3xnGe6p/+sx2PPOOLOsk5DeqGv5HtlY7ISauFFRXW8H+jdhevZm1n/j4Je/Z+bbIziSg==} - engines: {node: '>=18.13.0'} - peerDependencies: - express: '*' - fastify: '*' - h3: '*' - next: '*' - tailwindcss: '*' - peerDependenciesMeta: - express: - optional: true - fastify: - optional: true - h3: - optional: true - next: - optional: true - tailwindcss: - optional: true - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -6431,11 +6358,6 @@ snapshots: '@braintree/sanitize-url@6.0.4': {} - '@effect/schema@0.66.16(effect@3.2.8)(fast-check@3.19.0)': - dependencies: - effect: 3.2.8 - fast-check: 3.19.0 - '@emnapi/core@0.45.0': dependencies: tslib: 2.6.3 @@ -8284,14 +8206,6 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@uploadthing/mime-types@0.2.10': {} - - '@uploadthing/shared@6.7.5': - dependencies: - '@uploadthing/mime-types': 0.2.10 - effect: 3.2.8 - std-env: 3.7.0 - '@zxing/text-encoding@0.9.0': optional: true @@ -8675,8 +8589,6 @@ snapshots: ini: 1.3.8 proto-list: 1.2.4 - consola@3.2.3: {} - convert-source-map@2.0.0: {} copy-anything@3.0.5: @@ -9049,8 +8961,6 @@ snapshots: minimatch: 9.0.1 semver: 7.6.2 - effect@3.2.8: {} - electron-to-chromium@1.4.789: {} elkjs@0.9.3: {} @@ -9600,10 +9510,6 @@ snapshots: extend@3.0.2: {} - fast-check@3.19.0: - dependencies: - pure-rand: 6.1.0 - fast-deep-equal@2.0.1: {} fast-deep-equal@3.1.3: {} @@ -9691,10 +9597,6 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.3.0(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): - dependencies: - next: 14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - gensync@1.0.0-beta.2: {} get-intrinsic@1.2.4: @@ -11563,8 +11465,6 @@ snapshots: punycode@2.3.1: {} - pure-rand@6.1.0: {} - qs@6.12.1: dependencies: side-channel: 1.0.6 @@ -11964,8 +11864,6 @@ snapshots: sprintf-js@1.0.3: {} - std-env@3.7.0: {} - stream-chain@2.2.5: {} stream-json@1.8.0: @@ -12429,19 +12327,6 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 - uploadthing@6.12.0(next@14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(tailwindcss@3.4.3): - dependencies: - '@effect/schema': 0.66.16(effect@3.2.8)(fast-check@3.19.0) - '@uploadthing/mime-types': 0.2.10 - '@uploadthing/shared': 6.7.5 - consola: 3.2.3 - effect: 3.2.8 - fast-check: 3.19.0 - std-env: 3.7.0 - optionalDependencies: - next: 14.2.3(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - tailwindcss: 3.4.3 - uri-js@4.4.1: dependencies: punycode: 2.3.1