├── .eslintrc.cjs ├── .github └── workflows │ ├── check.yaml │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── apps ├── docs │ ├── .gitignore │ ├── astro.config.mjs │ ├── package.json │ ├── public │ │ └── favicon.svg │ ├── src │ │ ├── components │ │ │ ├── links.astro │ │ │ ├── navbar.astro │ │ │ ├── search.astro │ │ │ └── sidebar.astro │ │ ├── content │ │ │ ├── config.ts │ │ │ └── getting-started │ │ │ │ └── intro.md │ │ ├── env.d.ts │ │ ├── layouts │ │ │ ├── DocsLayout.astro │ │ │ ├── Layout.astro │ │ │ └── ProseLayout.astro │ │ ├── pages │ │ │ ├── [collection] │ │ │ │ └── [slug] │ │ │ │ │ └── index.astro │ │ │ ├── index.astro │ │ │ └── search.astro │ │ └── utils │ │ │ └── cn.ts │ ├── tailwind.config.mjs │ └── tsconfig.json └── web │ ├── .env.example │ ├── .eslintrc.cjs │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ └── images │ │ ├── hero-pattern_dark.svg │ │ └── hero-pattern_light.svg │ ├── reset.d.ts │ ├── src │ ├── app │ │ ├── (auth) │ │ │ ├── layout.tsx │ │ │ ├── login │ │ │ │ ├── github │ │ │ │ │ ├── callback │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ ├── login.tsx │ │ │ │ └── page.tsx │ │ │ ├── reset-password │ │ │ │ ├── [token] │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── reset-password.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── send-reset-email.tsx │ │ │ ├── signup │ │ │ │ ├── page.tsx │ │ │ │ └── signup.tsx │ │ │ └── verify-email │ │ │ │ ├── page.tsx │ │ │ │ └── verify-code.tsx │ │ ├── (landing) │ │ │ ├── _components │ │ │ │ ├── copy-to-clipboard.tsx │ │ │ │ ├── feature-icons.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── mobile-hamburger.tsx │ │ │ │ ├── mobile-navigation.tsx │ │ │ │ ├── site-footer.tsx │ │ │ │ └── theme-toggle.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── (main) │ │ │ ├── _actions │ │ │ │ └── revalidateDashboard.ts │ │ │ ├── _components │ │ │ │ ├── header.tsx │ │ │ │ └── user-dropdown.tsx │ │ │ ├── dashboard │ │ │ │ ├── _components │ │ │ │ │ ├── dashboard-nav.tsx │ │ │ │ │ ├── empty-state.tsx │ │ │ │ │ ├── form-card.tsx │ │ │ │ │ ├── formed-dialog.tsx │ │ │ │ │ ├── forms.tsx │ │ │ │ │ ├── new-form-dialog.tsx │ │ │ │ │ ├── post-card-skeleton.tsx │ │ │ │ │ └── posts-skeleton.tsx │ │ │ │ ├── billing │ │ │ │ │ ├── _components │ │ │ │ │ │ ├── billing-skeleton.tsx │ │ │ │ │ │ ├── billing.tsx │ │ │ │ │ │ └── manage-subscription-form.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── settings │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── profile-form.tsx │ │ │ │ │ └── sidebar-nav.tsx │ │ │ ├── form │ │ │ │ └── [id] │ │ │ │ │ ├── copy-button.tsx │ │ │ │ │ ├── delete-form-dialog.tsx │ │ │ │ │ ├── export-submissions-button.tsx │ │ │ │ │ ├── form-settings.tsx │ │ │ │ │ ├── image-preview-dialog.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── submissions-table.tsx │ │ │ ├── layout.tsx │ │ │ └── onboarding │ │ │ │ ├── form │ │ │ │ ├── code-example-step.tsx │ │ │ │ ├── create-form-dialog.tsx │ │ │ │ ├── create-form-step.tsx │ │ │ │ └── send-submission-button.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── stepper.tsx │ │ ├── api │ │ │ ├── health │ │ │ │ └── route.ts │ │ │ ├── mail │ │ │ │ └── route.ts │ │ │ ├── s │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── trpc │ │ │ │ └── [trpc] │ │ │ │ └── route.ts │ │ ├── icon.tsx │ │ ├── layout.tsx │ │ ├── robots.ts │ │ ├── s │ │ │ └── [formId] │ │ │ │ └── page.tsx │ │ └── sitemap.ts │ ├── components │ │ ├── copy-button.tsx │ │ ├── icons.tsx │ │ ├── loading-button.tsx │ │ ├── password-input.tsx │ │ ├── responsive-dialog.tsx │ │ ├── submit-button.tsx │ │ └── theme-provider.tsx │ ├── lib │ │ ├── email │ │ │ ├── index.ts │ │ │ ├── mailer.ts │ │ │ └── templates │ │ │ │ ├── email-verification.tsx │ │ │ │ ├── new-submission.tsx │ │ │ │ └── reset-password.tsx │ │ ├── highlight-code.ts │ │ ├── hooks │ │ │ ├── use-copy-to-clipboard.ts │ │ │ ├── use-debounce.ts │ │ │ └── use-media-query.ts │ │ ├── themes │ │ │ └── dark.ts │ │ ├── trpc │ │ │ ├── react.tsx │ │ │ └── server.ts │ │ ├── upload-file.ts │ │ └── verify-request.ts │ ├── middleware.ts │ └── styles │ │ └── globals.css │ ├── tailwind.config.ts │ └── tsconfig.json ├── docker └── docker-compose.yml ├── package.json ├── packages ├── api │ ├── .eslintrc.cjs │ ├── index.ts │ ├── package.json │ ├── routers │ │ ├── form.ts │ │ ├── formData.ts │ │ ├── stripe.ts │ │ └── user.ts │ ├── trpc.ts │ └── tsconfig.json ├── auth │ ├── .eslintrc.cjs │ ├── actions │ │ ├── index.ts │ │ ├── login.ts │ │ ├── logout.ts │ │ ├── resend-verification-email.ts │ │ ├── reset-password.ts │ │ ├── send-password-reset-link.ts │ │ ├── signup.ts │ │ ├── utils.ts │ │ └── verify-email.ts │ ├── auth.ts │ ├── index.ts │ ├── lucia.ts │ ├── package.json │ ├── providers │ │ └── github.ts │ ├── tsconfig.json │ └── validators │ │ └── auth.ts ├── config │ ├── eslint │ │ ├── .eslintrc.cjs │ │ ├── base.js │ │ ├── next.js │ │ ├── package.json │ │ ├── react.js │ │ └── tsconfig.json │ ├── tailwind │ │ ├── .eslintrc.cjs │ │ ├── package.json │ │ ├── src │ │ │ └── preset.ts │ │ └── tsconfig.json │ └── tsconfig │ │ ├── base.json │ │ ├── next.json │ │ ├── package.json │ │ └── react.json ├── core │ ├── .eslintrc.cjs │ ├── package.json │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── db │ ├── .eslintrc.cjs │ ├── drizzle.config.ts │ ├── drizzle │ │ ├── 0000_known_luckman.sql │ │ ├── 0001_military_rictor.sql │ │ └── meta │ │ │ ├── 0000_snapshot.json │ │ │ ├── 0001_snapshot.json │ │ │ └── _journal.json │ ├── index.ts │ ├── migrate.ts │ ├── package.json │ ├── schema │ │ ├── email-verification.ts │ │ ├── form-data.ts │ │ ├── forms.ts │ │ ├── index.ts │ │ ├── oauth.ts │ │ ├── onboarding-forms.ts │ │ ├── password-reset-tokens.ts │ │ ├── relations.ts │ │ ├── sessions.ts │ │ └── users.ts │ └── tsconfig.json ├── env │ ├── .eslintrc.cjs │ ├── env.d.ts │ ├── index.ts │ ├── package.json │ └── tsconfig.json ├── ui │ ├── .eslintrc.cjs │ ├── package.json │ ├── postcss.config.cjs │ ├── primitives │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast │ │ │ ├── index.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ └── use-toast.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ └── typography.tsx │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── utils │ │ └── cn.ts └── utils │ ├── .eslintrc.cjs │ ├── flatten-object.ts │ ├── generate-id.ts │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ └── url.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.js ├── tsconfig.json └── turbo.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('eslint').Linter.Config} */ 2 | const config = { 3 | ignorePatterns: ['apps/**', 'packages/**'], 4 | extends: ['formbase/base'], 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: Type check and lint 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | typecheck-and-lint: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: pnpm/action-setup@v2 15 | name: Install pnpm 16 | with: 17 | version: 9.0.6 18 | run_install: false 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: 20.x 22 | 23 | - name: Install dependencies 24 | run: pnpm install 25 | 26 | - name: Type check and lint 27 | run: pnpm typecheck && pnpm lint 28 | env: 29 | # use dummy env variables to bypass t3-env check 30 | DATABASE_URL: postgresql://test:xxxx@xxxxxxxxx:3306/test 31 | SMTP_HOST: host 32 | SMTP_PORT: 587 33 | SMTP_USER: user 34 | SMTP_PASSWORD: password 35 | NEXT_PUBLIC_APP_URL: http://localhost:3000 36 | ALLOW_SIGNIN_SIGNUP: true 37 | AUTH_GITHUB_ID: client_id 38 | AUTH_GITHUB_SECRET: client_secret 39 | STRIPE_API_KEY: stripe_api_key 40 | STRIPE_WEBHOOK_SECRET: stripe_webhook_secret 41 | STRIPE_PRO_MONTHLY_PLAN_ID: stripe_pro_monthly_plan_id 42 | MINIO_ENDPOINT: minio_endpoint 43 | MINIO_ACCESS_KEY: minio_access_key 44 | MINIO_SECRET_KEY: minio_secret_key 45 | MINIO_BUCKET: minio_bucket 46 | MINIO_PORT: 9000 47 | MINIO_USESSL: false 48 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Production 2 | on: 3 | push: 4 | branches: ['release'] 5 | 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Deploy to Coolify 11 | run: | 12 | curl --request GET '${{ secrets.COOLIFY_WEBHOOK }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}' 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 | An open-source form backend for form handling, notifications, secure file uploads, and integrations. 5 |
6 | 7 |8 | Introduction · 9 | Tech Stack · 10 | Self-hosting · 11 | Contributing 12 |
13 | 14 |8 | Formbase makes it easy to handle your HTML form submissions. Simplify your 9 | work with easy form management, automatic notifications, secure file 10 | uploads, and smooth integrations. Create a form and start accepting 11 | submissions right away. 12 |
13 | 14 |19 | Create a form and start accepting submissions instantly without any 20 | configuration. 21 |
22 |27 | Get real-time notifications every time your form receives a new entry. 28 |
29 |34 | Build and manage a team to work together seamlessly on form projects. 35 |
36 |40 |
41 | This documentation site is adapted from the SelfhostHQ by Shayan. 45 |
46 | 47 |61 | {e.description} 62 |
63 | 64 |65 | © {new Date().getFullYear()} Eight Labs. All rights reserved. 66 |
67 |{message}
40 |
38 | {JSON.stringify(data, null, 2)}
39 |
40 | ),
41 | });
42 | }
43 |
44 | return (
45 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/apps/web/src/app/(main)/dashboard/_components/forms.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { use } from 'react';
4 |
5 | import { type RouterOutputs } from '@formbase/api';
6 |
7 | import { EmptyFormState } from './empty-state';
8 | import { FormCard } from './form-card';
9 |
10 | interface FormsProps {
11 | promises: Promise<
12 | // [RouterOutputs['form']['userForms'], RouterOutputs['stripe']['getPlan']]
13 | [RouterOutputs['form']['userForms']]
14 | >;
15 | }
16 |
17 | export function Forms({ promises }: FormsProps) {
18 | const [forms] = use(promises);
19 |
20 | return (
21 | 33 | // Manage your billing and subscription 34 | //
35 | //27 | Manage your forms endpoints 28 |
29 |27 | Manage your account settings 28 |
29 |29 | Manage your profile settings. 30 |
31 |Use the new endpoint to recieve submissions
16 | 17 | {formId === null ? ( 18 |22 | <>{`https://formbase.dev/s/${formId}`}> 23 |25 |24 |
14 | Follow these steps to get your first form submission 15 |
16 |