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