Repository: Jaaneek/t3-supabase-app-router Branch: main Commit: d7cd1fd2a4b6 Files: 69 Total size: 104.2 KB Directory structure: gitextract_wqkuwtlm/ ├── .eslintrc.cjs ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── ci.yml │ ├── production.yaml │ └── staging.yaml ├── .gitignore ├── README.md ├── components.json ├── next.config.mjs ├── package.json ├── postcss.config.cjs ├── prettier.config.mjs ├── prisma/ │ └── schema.prisma ├── src/ │ ├── app/ │ │ ├── (auth)/ │ │ │ ├── _components/ │ │ │ │ └── DevLoginButtons.tsx │ │ │ ├── layout.tsx │ │ │ └── login/ │ │ │ └── page.tsx │ │ ├── (authenticatedRoutes)/ │ │ │ ├── authenticated/ │ │ │ │ └── page.tsx │ │ │ └── layout.tsx │ │ ├── _components/ │ │ │ └── ServerDataStreaming.tsx │ │ ├── api/ │ │ │ └── trpc/ │ │ │ └── [trpc]/ │ │ │ └── route.ts │ │ ├── layout.tsx │ │ └── page.tsx │ ├── components/ │ │ ├── FormInput/ │ │ │ └── FormInput.tsx │ │ ├── Icons/ │ │ │ ├── Icons.tsx │ │ │ └── index.ts │ │ ├── Loading/ │ │ │ ├── Loading.tsx │ │ │ └── index.ts │ │ ├── PrefetchTRPCQuery/ │ │ │ └── PrefetchTRPCQuery.tsx │ │ ├── PrivateRoute/ │ │ │ ├── PrivateRoute.tsx │ │ │ └── PrivateRouteBase.tsx │ │ ├── PublicRoute/ │ │ │ └── PublicRoute.tsx │ │ ├── TailwindIndicator/ │ │ │ ├── TailwindIndicator.tsx │ │ │ └── index.ts │ │ └── ui/ │ │ ├── button.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── use-toast.ts │ ├── env.mjs │ ├── providers/ │ │ ├── AnalyticsProvider/ │ │ │ └── AnalyticsProvider.tsx │ │ ├── AuthProvider/ │ │ │ ├── AuthProvider.tsx │ │ │ ├── withPrivateRoute.tsx │ │ │ └── withPublicRoute.tsx │ │ └── index.tsx │ ├── server/ │ │ ├── api/ │ │ │ ├── root.ts │ │ │ ├── routers/ │ │ │ │ ├── auth.ts │ │ │ │ └── example.ts │ │ │ └── trpc.ts │ │ ├── db.ts │ │ └── supabase/ │ │ ├── supabaseClient.ts │ │ └── supabaseTypes.ts │ ├── styles/ │ │ └── globals.css │ ├── trpc/ │ │ ├── react.tsx │ │ ├── server.ts │ │ └── shared.ts │ └── utils/ │ ├── auth.ts │ ├── cn.ts │ └── getQueryClient.ts ├── supabase/ │ ├── .gitignore │ ├── config.toml │ ├── functions/ │ │ └── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── migrations/ │ │ └── 20231004185846_initial_profiles.sql │ └── seed.sql ├── tailwind.config.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.cjs ================================================ /** @type {import("eslint").Linter.Config} */ const config = { root: true, extends: [ "next", "next/core-web-vitals", "plugin:@typescript-eslint/recommended", ], plugins: ["react", "@typescript-eslint", "unused-imports"], overrides: [ { files: ["*.ts", "*.tsx", "*.js"], parser: "@typescript-eslint/parser", }, ], rules: { "@typescript-eslint/no-misused-promises": "off", "@typescript-eslint/consistent-type-imports": [ "warn", { prefer: "type-imports", fixStyle: "inline-type-imports", }, ], "import/prefer-default-export": "off", "react/jsx-filename-extension": ["warn", { extensions: [".tsx"] }], "react/jsx-uses-react": "off", "react/react-in-jsx-scope": "off", "react/jsx-curly-brace-presence": [ "warn", { props: "never", children: "never" }, ], "@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/no-unused-vars": "off", "no-param-reassign": ["error"], "unused-imports/no-unused-imports": "error", "unused-imports/no-unused-vars": [ "warn", { vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_", }, ], "padding-line-between-statements": [ "error", { blankLine: "always", prev: ["const", "let", "var", "directive"], next: "*", }, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"], }, { blankLine: "always", prev: "*", next: "return" }, { blankLine: "always", prev: ["block", "block-like"], next: "*" }, { blankLine: "always", prev: "*", next: ["function"] }, { blankLine: "always", prev: "import", next: ["const", "let", "var", "function", "export", "block-like"], }, ], "no-nested-ternary": "error", }, }; module.exports = config; ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Motivation and context ## Description ## Steps to reproduce the behavior ## Screenshots (if appropriate) ================================================ FILE: .github/workflows/ci.yml ================================================ defaults: run: working-directory: ./ name: CI on pull request on: [pull_request] jobs: prettier-eslint: name: prettier-eslint runs-on: ubuntu-latest strategy: matrix: node-version: [18] env: SKIP_ENV_VALIDATION: true steps: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2 with: version: 8 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: "pnpm" - name: Install pnpm run: pnpm install - name: Run Lint run: pnpm lint ================================================ FILE: .github/workflows/production.yaml ================================================ name: Deploy Migrations to Production on: push: branches: - prod workflow_dispatch: jobs: deploy: runs-on: ubuntu-22.04 env: SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} SUPABASE_DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }} PRODUCTION_PROJECT_ID: steps: - uses: actions/checkout@v3 - uses: supabase/setup-cli@v1 - run: | supabase link --project-ref $PRODUCTION_PROJECT_ID supabase db push ================================================ FILE: .github/workflows/staging.yaml ================================================ name: Deploy Migrations to Staging on: push: branches: - staging workflow_dispatch: jobs: deploy: runs-on: ubuntu-22.04 env: SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} SUPABASE_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }} STAGING_PROJECT_ID: steps: - uses: actions/checkout@v3 - uses: supabase/setup-cli@v1 - run: | supabase link --project-ref $STAGING_PROJECT_ID supabase db push ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # database /prisma/db.sqlite /prisma/db.sqlite-journal # next.js /.next/ /out/ next-env.d.ts # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* .pnpm-debug.log* # local env files # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables .env .env*.local # vercel .vercel # typescript *.tsbuildinfo ================================================ FILE: README.md ================================================ # T3 stack + Supabase + App directory This project is Edge ready (Vercel Edge runtime) This is a starter project/boilerplate to start out with: - TRPC - App directory/router - Prisma - Supabase (Auth, Storage, Serverless Queries) - Tailwind - Edge Ready - Umami analytics It allows us to call database in server components through supabase-js, for client component we are using trpc+prisma due to the superior DX ## Projects using this starter - PortfolioQuiz [Website](https://www.portfolio-quiz.com/) - FreeLogo.dev [Website](https://www.freelogo.dev/) - FeastQR [Repo](https://github.com/jakubczarnowski/FeastQR) [Website](https://www.feastqr.com) - Instagram Clone [Repo](https://github.com/jakubczarnowski/instagram-clone) [Website](https://instagram-clone-eight-mu.vercel.app/) ## What's next? How do I make an app with this? - Clone this project - Run ``` pnpm install ``` - Copy the .env.example into .env and fill out the envs ### Initial Setup ## If you want to develop on local supabase instance, follow the steps below: Then go to supabase/config.toml file and change your service name. Start the database: - supabase start - pnpm prepare:local ## If you want to develop on remote supabase instance, follow the steps below: Connect supabase to remote instance: - supabase link --project-ref <_your_project_id_> - pnpm prepare:remote ## Common steps - Fill out environment variables - Create Secrets on Github #### If you want to create migrations by hand, go ahead and use this command: - supabase migration new <_migration_name_> Then go to supabase/migrations folder and add your SQL there. #### If you want to make changes with studio, use - pnpm db:diff <_migration_name_> ## Run these initial commands Every time you change something on local instance: ``` pnpm prepare:local ``` - If you develop on cloud supabase run: ``` pnpm prepare:remote ``` - Run the project ``` pnpm dev ``` If you are not familiar with the different technologies used in this project, please refer to the respective docs. - [Next.js app router](https://nextjs.org/docs) - [Prisma](https://prisma.io) - [Tailwind CSS](https://tailwindcss.com) - [tRPC](https://trpc.io) - [Supabase](https://supabase.com/docs) ## Authors 👤 **Milosz Jankiewicz** - Twitter: [@twitter.com/jaaneek/](https://twitter.com/jaaneek) - Github: [@Jaaneek](https://github.com/Jaaneek) - LinkedIn: [@https://www.linkedin.com/in/jaaneek](https://www.linkedin.com/in/mi%C5%82osz-jankiewicz-554562168/) 👤 **Jakub Czarnowski** - Twitter: [@twitter.com/charnowsky/](https://twitter.com/charnowsky) - Github: [@jakubczarnowski](https://github.com/jakubczarnowski) - LinkedIn: [@https://www.linkedin.com/in/czarnowskijakub/](https://www.linkedin.com/in/czarnowskijakub/) ## Learn More To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: - [Documentation](https://create.t3.gg/) - [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! ## How do I deploy this? Follow deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. ================================================ FILE: components.json ================================================ { "$schema": "https://ui.shadcn.com/schema.json", "style": "default", "rsc": true, "tsx": true, "tailwind": { "config": "tailwind.config.ts", "css": "src/styles/globals.css", "baseColor": "slate", "cssVariables": true }, "aliases": { "components": "~/components", "utils": "~/utils/cn" } } ================================================ FILE: next.config.mjs ================================================ /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful * for Docker builds. */ await import("./src/env.mjs"); /** @type {import("next").NextConfig} */ const config = { reactStrictMode: true, /** * If you are using `appDir` then you must comment the below `i18n` config out. * * @see https://github.com/vercel/next.js/issues/41980 */ i18n: { locales: ["en"], defaultLocale: "en", }, }; export default config; ================================================ FILE: package.json ================================================ { "name": "t3-supabase-app-router-enhanced", "version": "0.1.0", "private": true, "scripts": { "build": "next build", "db:push": "prisma db push", "dev": "next dev", "postinstall": "prisma generate", "start": "next start", "lint": "next lint && pnpm check-types", "check-types": "tsc --noEmit", "update-types-local": "npx supabase gen types typescript --local > src/server/supabase/supabaseTypes.ts", "update-types-remote": "npx supabase gen types typescript --project-id \"\" --schema public > src/server/supabase/supabaseTypes.ts", "prepare:local": "pnpm update-types-local && pnpm prisma db pull && pnpm prisma generate && pnpm prisma-case-format --file prisma/schema.prisma && pnpm prisma generate", "prepare:remote": "pnpm update-types-remote && pnpm prisma db pull && pnpm prisma generate && pnpm prisma-case-format --file prisma/schema.prisma && pnpm prisma generate", "db:reset": "npx supabase db reset", "db:start": "npx supabase start", "db:stop": "npx supabase stop", "db:diff": "npx supabase db diff -f" }, "dependencies": { "@prisma/client": "^5.6.0", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@supabase/auth-helpers-nextjs": "^0.8.7", "@supabase/supabase-js": "^2.39.0", "@t3-oss/env-nextjs": "^0.6.1", "@tanstack/react-query": "^4.36.1", "@tanstack/react-query-devtools": "^5.12.1", "@trpc/client": "^10.44.1", "@trpc/next": "^10.44.1", "@trpc/react-query": "^10.44.1", "@trpc/server": "^10.44.1", "@vercel/analytics": "^1.1.1", "accept-language": "^3.0.18", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "lucide-react": "^0.284.0", "next": "^14.0.3", "next-themes": "^0.2.1", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.49.2", "superjson": "^1.13.3", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" }, "devDependencies": { "@types/eslint": "^8.44.8", "@types/node": "^18.19.0", "@types/react": "^18.2.39", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^6.13.1", "@typescript-eslint/parser": "^6.13.1", "autoprefixer": "^10.4.16", "eslint": "^8.54.0", "eslint-config-next": "^13.5.6", "eslint-plugin-unused-imports": "^3.0.0", "postcss": "^8.4.31", "prettier": "^3.1.0", "prettier-plugin-tailwindcss": "^0.5.7", "prisma": "^5.6.0", "prisma-case-format": "^1.7.3", "tailwindcss": "^3.3.5", "typescript": "^5.3.2" }, "ct3aMetadata": { "initVersion": "7.20.2" }, "packageManager": "pnpm@8.8.0" } ================================================ FILE: postcss.config.cjs ================================================ const config = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; module.exports = config; ================================================ FILE: prettier.config.mjs ================================================ /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').options} */ const config = { plugins: ["prettier-plugin-tailwindcss"], }; export default config; ================================================ FILE: prisma/schema.prisma ================================================ generator client { provider = "prisma-client-js" previewFeatures = ["multiSchema"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DIRECT_URL") schemas = ["auth", "public"] } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model AuditLogEntries { instanceId String? @map("instance_id") @db.Uuid id String @id @db.Uuid payload Json? @db.Json createdAt DateTime? @map("created_at") @db.Timestamptz(6) ipAddress String @default("") @map("ip_address") @db.VarChar(64) @@index([instanceId], map: "audit_logs_instance_id_idx") @@map("audit_log_entries") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model FlowState { id String @id @db.Uuid userId String? @map("user_id") @db.Uuid authCode String @map("auth_code") codeChallengeMethod CodeChallengeMethod @map("code_challenge_method") codeChallenge String @map("code_challenge") providerType String @map("provider_type") providerAccessToken String? @map("provider_access_token") providerRefreshToken String? @map("provider_refresh_token") createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) authenticationMethod String @map("authentication_method") @@index([authCode], map: "idx_auth_code") @@index([userId, authenticationMethod], map: "idx_user_id_auth_method") @@map("flow_state") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model Identities { id String userId String @map("user_id") @db.Uuid identityData Json @map("identity_data") provider String lastSignInAt DateTime? @map("last_sign_in_at") @db.Timestamptz(6) createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) email String? @default(dbgenerated("lower((identity_data ->> 'email'::text))")) users Users @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@id([provider, id]) @@index([email]) @@index([userId]) @@map("identities") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model Instances { id String @id @db.Uuid uuid String? @db.Uuid rawBaseConfig String? @map("raw_base_config") createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) @@map("instances") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model MfaAmrClaims { sessionId String @map("session_id") @db.Uuid createdAt DateTime @map("created_at") @db.Timestamptz(6) updatedAt DateTime @map("updated_at") @db.Timestamptz(6) authenticationMethod String @map("authentication_method") id String @id(map: "amr_id_pk") @db.Uuid sessions Sessions @relation(fields: [sessionId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@unique([sessionId, authenticationMethod], map: "mfa_amr_claims_session_id_authentication_method_pkey") @@map("mfa_amr_claims") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model MfaChallenges { id String @id @db.Uuid factorId String @map("factor_id") @db.Uuid createdAt DateTime @map("created_at") @db.Timestamptz(6) verifiedAt DateTime? @map("verified_at") @db.Timestamptz(6) ipAddress String @map("ip_address") @db.Inet mfaFactors MfaFactors @relation(fields: [factorId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "mfa_challenges_auth_factor_id_fkey") @@map("mfa_challenges") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model MfaFactors { id String @id @db.Uuid userId String @map("user_id") @db.Uuid friendlyName String? @map("friendly_name") factorType FactorType @map("factor_type") status FactorStatus @map("status") createdAt DateTime @map("created_at") @db.Timestamptz(6) updatedAt DateTime @map("updated_at") @db.Timestamptz(6) secret String? mfaChallenges MfaChallenges[] users Users @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@index([userId, createdAt], map: "factor_id_created_at_idx") @@map("mfa_factors") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model RefreshTokens { instanceId String? @map("instance_id") @db.Uuid id BigInt @id @default(autoincrement()) token String? @unique(map: "refresh_tokens_token_unique") @db.VarChar(255) userId String? @map("user_id") @db.VarChar(255) revoked Boolean? createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) parent String? @db.VarChar(255) sessionId String? @map("session_id") @db.Uuid sessions Sessions? @relation(fields: [sessionId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@index([instanceId]) @@index([instanceId, userId]) @@index([parent]) @@index([sessionId, revoked]) @@map("refresh_tokens") @@schema("auth") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model SamlProviders { id String @id @db.Uuid ssoProviderId String @map("sso_provider_id") @db.Uuid entityId String @unique @map("entity_id") metadataXml String @map("metadata_xml") metadataUrl String? @map("metadata_url") attributeMapping Json? @map("attribute_mapping") createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) ssoProviders SsoProviders @relation(fields: [ssoProviderId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@index([ssoProviderId]) @@map("saml_providers") @@schema("auth") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model SamlRelayStates { id String @id @db.Uuid ssoProviderId String @map("sso_provider_id") @db.Uuid requestId String @map("request_id") forEmail String? @map("for_email") redirectTo String? @map("redirect_to") fromIpAddress String? @map("from_ip_address") @db.Inet createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) ssoProviders SsoProviders @relation(fields: [ssoProviderId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@index([forEmail]) @@index([ssoProviderId]) @@map("saml_relay_states") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model SchemaMigrations { version String @id @db.VarChar(255) @@map("schema_migrations") @@schema("auth") } /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments model Sessions { id String @id @db.Uuid userId String @map("user_id") @db.Uuid createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) factorId String? @map("factor_id") @db.Uuid aal AalLevel? @map("aal") notAfter DateTime? @map("not_after") @db.Timestamptz(6) mfaAmrClaims MfaAmrClaims[] refreshTokens RefreshTokens[] users Users @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@index([userId]) @@index([userId, createdAt], map: "user_id_created_at_idx") @@map("sessions") @@schema("auth") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments /// This model contains an expression index which requires additional setup for migrations. Visit https://pris.ly/d/expression-indexes for more info. model SsoDomains { id String @id @db.Uuid ssoProviderId String @map("sso_provider_id") @db.Uuid domain String createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) ssoProviders SsoProviders @relation(fields: [ssoProviderId], references: [id], onDelete: Cascade, onUpdate: NoAction) @@index([ssoProviderId]) @@map("sso_domains") @@schema("auth") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments /// This model contains an expression index which requires additional setup for migrations. Visit https://pris.ly/d/expression-indexes for more info. model SsoProviders { id String @id @db.Uuid resourceId String? @map("resource_id") createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) samlProviders SamlProviders[] samlRelayStates SamlRelayStates[] ssoDomains SsoDomains[] @@map("sso_providers") @@schema("auth") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. /// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments /// This model contains an expression index which requires additional setup for migrations. Visit https://pris.ly/d/expression-indexes for more info. model Users { instanceId String? @map("instance_id") @db.Uuid id String @id @db.Uuid aud String? @db.VarChar(255) role String? @db.VarChar(255) email String? @db.VarChar(255) encryptedPassword String? @map("encrypted_password") @db.VarChar(255) emailConfirmedAt DateTime? @map("email_confirmed_at") @db.Timestamptz(6) invitedAt DateTime? @map("invited_at") @db.Timestamptz(6) confirmationToken String? @map("confirmation_token") @db.VarChar(255) confirmationSentAt DateTime? @map("confirmation_sent_at") @db.Timestamptz(6) recoveryToken String? @map("recovery_token") @db.VarChar(255) recoverySentAt DateTime? @map("recovery_sent_at") @db.Timestamptz(6) emailChangeTokenNew String? @map("email_change_token_new") @db.VarChar(255) emailChange String? @map("email_change") @db.VarChar(255) emailChangeSentAt DateTime? @map("email_change_sent_at") @db.Timestamptz(6) lastSignInAt DateTime? @map("last_sign_in_at") @db.Timestamptz(6) rawAppMetaData Json? @map("raw_app_meta_data") rawUserMetaData Json? @map("raw_user_meta_data") isSuperAdmin Boolean? @map("is_super_admin") createdAt DateTime? @map("created_at") @db.Timestamptz(6) updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) phone String? @unique phoneConfirmedAt DateTime? @map("phone_confirmed_at") @db.Timestamptz(6) phoneChange String? @default("") @map("phone_change") phoneChangeToken String? @default("") @map("phone_change_token") @db.VarChar(255) phoneChangeSentAt DateTime? @map("phone_change_sent_at") @db.Timestamptz(6) confirmedAt DateTime? @default(dbgenerated("LEAST(email_confirmed_at, phone_confirmed_at)")) @map("confirmed_at") @db.Timestamptz(6) emailChangeTokenCurrent String? @default("") @map("email_change_token_current") @db.VarChar(255) emailChangeConfirmStatus Int? @default(0) @map("email_change_confirm_status") @db.SmallInt bannedUntil DateTime? @map("banned_until") @db.Timestamptz(6) reauthenticationToken String? @default("") @map("reauthentication_token") @db.VarChar(255) reauthenticationSentAt DateTime? @map("reauthentication_sent_at") @db.Timestamptz(6) isSsoUser Boolean @default(false) @map("is_sso_user") deletedAt DateTime? @map("deleted_at") @db.Timestamptz(6) identities Identities[] mfaFactors MfaFactors[] sessions Sessions[] profiles Profiles? @@index([instanceId]) @@map("users") @@schema("auth") } /// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. /// This model contains row level security and requires additional setup for migrations. Visit https://pris.ly/d/row-level-security for more info. model Profiles { id String @id @db.Uuid updatedAt DateTime? @map("updated_at") @db.Timestamptz(6) email String? @unique fullName String? @map("full_name") avatarUrl String? @map("avatar_url") users Users @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: NoAction) @@map("profiles") @@schema("public") } enum AalLevel { aal1 aal2 aal3 @@map("aal_level") @@schema("auth") } enum CodeChallengeMethod { s256 plain @@map("code_challenge_method") @@schema("auth") } enum FactorStatus { unverified verified @@map("factor_status") @@schema("auth") } enum FactorType { totp webauthn @@map("factor_type") @@schema("auth") } ================================================ FILE: src/app/(auth)/_components/DevLoginButtons.tsx ================================================ "use client"; import { supabase } from "~/server/supabase/supabaseClient"; const testAccounts = [ { email: "random@gmail.com", password: "testPassword" }, { email: "random2@gmail.com", password: "testPassword2" }, ]; export const DevLoginButtons = () => { return (
{testAccounts.map((account, index) => (
))}
); }; ================================================ FILE: src/app/(auth)/layout.tsx ================================================ import { type PropsWithChildren } from "react"; import { PublicRoute } from "~/components/PublicRoute/PublicRoute"; const Layout = async ({ children }: PropsWithChildren) => { return {children}; }; export default Layout; ================================================ FILE: src/app/(auth)/login/page.tsx ================================================ "use client"; import { type Provider } from "@supabase/supabase-js"; import { Icons } from "~/components/Icons"; import { Button } from "~/components/ui/button"; import { supabase } from "~/server/supabase/supabaseClient"; import { DevLoginButtons } from "../_components/DevLoginButtons"; const Page = () => { const signInWithOauth = (provider: Provider) => { void supabase().auth.signInWithOAuth({ provider: provider, }); }; return (

Login

{process.env.NEXT_PUBLIC_VERCEL_ENV !== "production" && ( )}
); }; export default Page; ================================================ FILE: src/app/(authenticatedRoutes)/authenticated/page.tsx ================================================ "use client"; import { api } from "~/trpc/react"; const AuthenticatedExample = () => { const { data } = api.auth.getProfile.useQuery(); return (

Authenticated Route Example

User Information

Email: {data?.email}
Name: {data?.fullName}
); }; export default AuthenticatedExample; ================================================ FILE: src/app/(authenticatedRoutes)/layout.tsx ================================================ import { type PropsWithChildren } from "react"; import { PrefetchTRPCQuery } from "~/components/PrefetchTRPCQuery/PrefetchTRPCQuery"; import { PrivateRoute } from "~/components/PrivateRoute/PrivateRoute"; export default function Layout({ children }: PropsWithChildren) { return ( {children} ); } ================================================ FILE: src/app/_components/ServerDataStreaming.tsx ================================================ import { api } from "~/trpc/server"; export async function ServerDataStreaming() { const data = await api.example.hello.query({ text: "from tRPC" }); await new Promise((resolve) => setTimeout(resolve, 1000)); return

{data.greeting}

; } ================================================ FILE: src/app/api/trpc/[trpc]/route.ts ================================================ import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { type NextRequest } from "next/server"; import { env } from "~/env.mjs"; import { appRouter } from "~/server/api/root"; import { createTRPCContext } from "~/server/api/trpc"; /** * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when * handling a HTTP request (e.g. when you make requests from Client Components). */ const createContext = async (req: NextRequest) => { return createTRPCContext({ headers: req.headers, }); }; const handler = (req: NextRequest) => fetchRequestHandler({ endpoint: "/api/trpc", req, router: appRouter, createContext: () => createContext(req), onError: env.NODE_ENV === "development" ? ({ path, error }) => { console.error( `❌ tRPC failed on ${path ?? ""}: ${error.message}`, ); } : undefined, }); export { handler as GET, handler as POST }; ================================================ FILE: src/app/layout.tsx ================================================ import { TailwindIndicator } from "~/components/TailwindIndicator"; import { Providers } from "~/providers"; import "~/styles/globals.css"; import { cn } from "~/utils/cn"; import { Roboto } from "next/font/google"; import { Toaster } from "~/components/ui/toaster"; import { getServerUser } from "~/utils/auth"; import { AuthProvider } from "~/providers/AuthProvider/AuthProvider"; import { TRPCReactProvider } from "~/trpc/react"; import { headers } from "next/headers"; export const metadata = { title: "t3-app-dir-supabase", description: "Boilerplate for t3-app-dir-supabase.", }; const font = Roboto({ weight: ["100", "300", "400", "500", "700", "900"], subsets: ["latin"], }); async function RootLayout({ children }: { children: React.ReactNode }) { const user = await getServerUser(); return ( <> {children} ); } export default RootLayout; ================================================ FILE: src/app/page.tsx ================================================ import Link from "next/link"; import { Suspense } from "react"; import { ServerDataStreaming } from "./_components/ServerDataStreaming"; const Homepage = async () => { return (

Nextjs 14 App router starter

Based On

Create T3 App

First Steps →

Just the basics - Everything you need to know to set up your database and authentication.

Documentation →

Learn more about Create T3 App, the libraries it uses, and how to deploy it.
Login Authenticated Route Example
Streaming TRPC Query...

} >
); }; export default Homepage; ================================================ FILE: src/components/FormInput/FormInput.tsx ================================================ import { FormControl, FormDescription, FormItem, FormLabel, FormMessage, } from "../ui/form"; export type FormInputProps = { label?: string; description?: string; children: React.ReactNode; }; export const FormInput = ({ label, description, children }: FormInputProps) => ( {label && {label}} {children} {description && {description}} ); ================================================ FILE: src/components/Icons/Icons.tsx ================================================ import { AlertTriangle, ArrowRight, Check, ChevronLeft, ChevronRight, Command, CreditCard, File, FileText, HelpCircle, Image, Laptop, Loader2, type LucideProps, Moon, MoreVertical, Pizza, Plus, Settings, SunMedium, Trash, Twitter, User, X, Zap, Palette, Truck, AlignHorizontalDistributeCenter, Type, Construction, type LucideIcon, Settings2, Edit, Star, Sparkle, MonitorSmartphone, Scaling, Sparkles, Download, Menu, Facebook, Instagram, Linkedin, Youtube, Lock, Flag, Languages, } from "lucide-react"; export type Icon = LucideIcon; export const Icons = { languages: Languages, logo: Command, close: X, spinner: Loader2, chevronLeft: ChevronLeft, chevronRight: ChevronRight, trash: Trash, post: FileText, page: File, media: Image, settings: Settings, billing: CreditCard, ellipsis: MoreVertical, add: Plus, warning: AlertTriangle, user: User, arrowRight: ArrowRight, help: HelpCircle, pizza: Pizza, sun: SunMedium, moon: Moon, laptop: Laptop, zap: Zap, palette: Palette, truck: Truck, type: Type, customize: AlignHorizontalDistributeCenter, construction: Construction, settings2: Settings2, edit: Edit, star: Star, sparkle: Sparkle, devices: MonitorSmartphone, scaling: Scaling, sparkles: Sparkles, download: Download, menu: Menu, facebook: Facebook, instagram: Instagram, linkedin: Linkedin, youtube: Youtube, lock: Lock, flag: Flag, gitHub: ({ ...props }) => ( ), google: ({ ...props }: LucideProps) => ( ), discord: ({ ...props }: LucideProps) => ( ), twitter: Twitter, check: Check, }; ================================================ FILE: src/components/Icons/index.ts ================================================ export * from "./Icons"; ================================================ FILE: src/components/Loading/Loading.tsx ================================================ import React from "react"; import { Icons } from "../Icons/Icons"; import { type LucideProps } from "lucide-react"; import { cn } from "~/utils/cn"; export const Spinner = ({ className, ...props }: LucideProps) => ( ); export const LoadingScreen = () => { return (
); }; ================================================ FILE: src/components/Loading/index.ts ================================================ export * from "./Loading"; ================================================ FILE: src/components/PrefetchTRPCQuery/PrefetchTRPCQuery.tsx ================================================ import { Hydrate, dehydrate } from "@tanstack/react-query"; import { api as serverApi } from "~/trpc/server"; import { getServerQueryClient } from "~/utils/getQueryClient"; import { type RouterInputs } from "~/trpc/shared"; type AccessPaths = { [K in keyof T]: { [L in keyof T[K]]: `${string & K}.${string & L}`; }[keyof T[K]]; }[keyof T]; type ValueTypeAt = P extends `${infer K}.${infer L}` ? K extends keyof T ? L extends keyof T[K] ? T[K][L] : never : never : never; type ParamsType> = ValueTypeAt< RouterInputs, T > extends void | undefined ? { params?: undefined } : { params: ValueTypeAt }; export const PrefetchTRPCQuery = async >({ children, queryName, params = undefined, }: { children: React.ReactNode; queryName: T; } & ParamsType) => { const queryClient = getServerQueryClient(); const [router, procedure] = queryName.split("."); try { // Just let the frontend handle it if it fails // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const data = await serverApi[router][procedure].query(params); await queryClient.prefetchQuery( [[router, procedure], { input: params, type: "query" }], () => data, ); const dehydratedState = dehydrate(queryClient); return {children}; } catch (e) { console.error(e, "PrefetchTRPCQuery failed"); return <>{children}; } }; ================================================ FILE: src/components/PrivateRoute/PrivateRoute.tsx ================================================ import { type PropsWithChildren } from "react"; import { getServerUser } from "~/utils/auth"; import { PrivateRouteBase } from "./PrivateRouteBase"; import { redirect } from "next/navigation"; export const PrivateRoute = async ({ children }: PropsWithChildren) => { const user = await getServerUser(); if (!user) redirect("/login"); return {children}; }; ================================================ FILE: src/components/PrivateRoute/PrivateRouteBase.tsx ================================================ "use client"; import { type PropsWithChildren } from "react"; import { withPrivateRoute } from "~/providers/AuthProvider/withPrivateRoute"; const PrivateRouteBaseComponent = ({ children }: PropsWithChildren) => children; export const PrivateRouteBase = withPrivateRoute(PrivateRouteBaseComponent); ================================================ FILE: src/components/PublicRoute/PublicRoute.tsx ================================================ "use client"; import { withPublicRoute } from "~/providers/AuthProvider/withPublicRoute"; const PublicRouteComponent = ({ children }: { children: React.ReactNode }) => children; export const PublicRoute = withPublicRoute(PublicRouteComponent); ================================================ FILE: src/components/TailwindIndicator/TailwindIndicator.tsx ================================================ export function TailwindIndicator() { if (process.env.NODE_ENV === "production") return null; return (
xs
sm
md
lg
xl
2xl
); } ================================================ FILE: src/components/TailwindIndicator/index.ts ================================================ export * from "./TailwindIndicator"; ================================================ FILE: src/components/ui/button.tsx ================================================ import * as React from "react"; import { type VariantProps, cva } from "class-variance-authority"; import { cn } from "~/utils/cn"; import { Spinner } from "../Loading"; const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "underline-offset-4 hover:underline text-primary", premium: "font-bold bg-clip-padding border-[2px] before:-m-[2px] rounded-md before:rounded-md border-transparent bg-white dark:bg-secondary text-primary hover:before:bg-gradient-to-r hover:before:from-pink-500 hover:before:to-purple-500 before:bg-gradient-to-r before:from-purple-500 before:to-pink-500 hover:bg-white/90 dark:hover:bg-secondary/90 relative before:content before:absolute before:z-[-1] before:inset-0 before:bg-primary before:transition-opacity before:duration-500 before:delay-100 hover:before:opacity-100", }, size: { default: "h-10 py-2 px-4", sm: "h-9 px-3 rounded-md", lg: "h-11 px-8 rounded-md", }, }, defaultVariants: { variant: "default", size: "default", }, }, ); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { loading?: boolean; } const Button = React.forwardRef( ({ className, variant, size, children, loading, ...props }, ref) => { return ( ); }, ); Button.displayName = "Button"; export { Button, buttonVariants }; ================================================ FILE: src/components/ui/dropdown-menu.tsx ================================================ "use client"; import * as React from "react"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; import { cn } from "~/utils/cn"; const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; const DropdownMenuGroup = DropdownMenuPrimitive.Group; const DropdownMenuPortal = DropdownMenuPrimitive.Portal; const DropdownMenuSub = DropdownMenuPrimitive.Sub; const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean; } >(({ className, inset, children, ...props }, ref) => ( {children} )); DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; const DropdownMenuSubContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; const DropdownMenuContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( )); DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; const DropdownMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean; } >(({ className, inset, ...props }, ref) => ( )); DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( {children} )); DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} )); DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean; } >(({ className, inset, ...props }, ref) => ( )); DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; const DropdownMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( ); }; DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, }; ================================================ FILE: src/components/ui/form.tsx ================================================ import * as React from "react"; import type * as LabelPrimitive from "@radix-ui/react-label"; import { Slot } from "@radix-ui/react-slot"; import { Controller, type ControllerProps, type FieldPath, type FieldValues, FormProvider, useFormContext, } from "react-hook-form"; import { cn } from "~/utils/cn"; import { Label } from "~/components/ui/label"; const Form = FormProvider; type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, > = { name: TName; }; const FormFieldContext = React.createContext( {} as FormFieldContextValue, ); const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, >({ ...props }: ControllerProps) => { return ( ); }; const useFormField = () => { const fieldContext = React.useContext(FormFieldContext); const itemContext = React.useContext(FormItemContext); const { getFieldState, formState } = useFormContext(); const fieldState = getFieldState(fieldContext.name, formState); if (!fieldContext) { throw new Error("useFormField should be used within "); } const { id } = itemContext; return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, }; }; type FormItemContextValue = { id: string; }; const FormItemContext = React.createContext( {} as FormItemContextValue, ); const FormItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({ className, ...props }, ref) => { const id = React.useId(); return (
); }); FormItem.displayName = "FormItem"; const FormLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { const { error, formItemId } = useFormField(); return (