Skip to content

Commit

Permalink
feat(cli): adds a new template, one-to-many (#15)
Browse files Browse the repository at this point in the history
* chore(one-to-many): copy of one-to-one

* feat(one-to-many): track account membership via join table

* chore(one-to-many): remove unused imports

* feat(one-to-many): add name field to accounts table

* feat(one-to-many): add logic for signing up and setting up a new account

* feat(one-to-many): add logic and basic UI for sending invites

* feat(one-to-many): display list of users and invites

* perf(one-to-many): batch db calls

* docs(one-to-many): add jsdocs to actions

* fix(one-to-many): remove debugs

* fix(one-to-many): some actions were missing use server

* fix(one-to-many): add inviterId to invite_tokens

* fix(one-to-many): allow removal of users from an account, in addition to removal of invites

* fix(one-to-many): perform some integrity checks before removing a user

* refactor(one-to-many): abstract repeated action checks into higher order functions

* fix(one-to-many): allow changing of user roles

* fix(one-to-many): add basic invite page

* fix(one-to-many): protect against expiry and existing account

* fix(one-to-many): check for invites when signing in a non-account holder user

* fix(one-to-many): allow accepting an invite

* fix(one-to-many): properly check for empty fetch result

* chore(one-to-many): tighten up type checking

* chore(one-to-many): linting errors

* chore(one-to-many): pnpm update

* chore(all): remove lockfiles

* fix(one-to-many): drop status column from users_accounts

* fix(one-to-many): status is no longer part of users_accounts

* refactor(one-to-many): add BASE_URL and cleanup env vars in general

* fix(one-to-many): fix env var called for BASE_URL

* fix(one-to-many): don't try and write to status

* fix(one-to-many): generally improve page styling

* fix(one-to-many): move invite email content to component

* fix(one-to-many): add account information to invite

* fix(one-to-many): send custom magic link emails

* chore(one-to-many): such lazy typing

* chore(one-to-manu): remove unused

* fix(one-to-many): use correct env var

* fix(one-to-many): better error messages

* docs(one-to-many): add missing jsdocs

* fix(one-to-many): redirect to /welcome after invite accept

* fix(one-to-many): don't leak email info

* fix(one-to-many): add role checks to role changes, deletions, and additions

* fix(one-to-many): remove unused import

* fix(one-to-many): disable role change select for users

* chore(one-to-many): move action to account folder

* refactor(one-to-many): simplify fetches when we know we expect only one row result

* fix(one-to-many): fix hydration errors

* fix(one-to-many): don't clear invite input on validation fails

* refactor(one-to-many): abstract invitation page components, and improve UX of buttons

* fix(one-to-many): update app welcome message

* chore(release): changeset

* fix(one-to-many): fix import errors
  • Loading branch information
jakeisonline authored Jan 15, 2025
1 parent 749e7a4 commit d5639d8
Show file tree
Hide file tree
Showing 105 changed files with 7,281 additions and 6,054 deletions.
6 changes: 6 additions & 0 deletions .changeset/four-ravens-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"next-auth-template-one-to-many": minor
"next-auth-template": minor
---

Adds one-to-many, a new template that allows multiple users to belong to one account. Also includes invite flows."
18 changes: 13 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@
"121:dev": "pnpm --filter next-auth-template-one-to-one run dev",
"121:build": "pnpm --filter next-auth-template-one-to-one run build",
"121:start": "pnpm --filter next-auth-template-one-to-one run start",
"121:lint": "pnpm --filter next-auth-template-one-to-one run lint"
"121:lint": "pnpm --filter next-auth-template-one-to-one run lint",
"12m:db:push": "pnpm --filter next-auth-template-one-to-many run db:push",
"12m:db:studio": "pnpm --filter next-auth-template-one-to-many run db:studio",
"12m:db:migrate": "pnpm --filter next-auth-template-one-to-many run db:migrate",
"12m:db:generate": "pnpm --filter next-auth-template-one-to-many run db:generate",
"12m:dev": "pnpm --filter next-auth-template-one-to-many run dev",
"12m:build": "pnpm --filter next-auth-template-one-to-many run build",
"12m:start": "pnpm --filter next-auth-template-one-to-many run start",
"12m:lint": "pnpm --filter next-auth-template-one-to-many run lint"
},
"bin": "./dist/index.js",
"files": [
Expand Down Expand Up @@ -54,12 +62,12 @@
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^22.10.3",
"@types/node": "^22.10.5",
"@types/prompts": "^2.4.9",
"eslint": "^8",
"eslint": "^8.57.1",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.8",
"prettier-plugin-tailwindcss": "^0.6.9",
"tsup": "^8.3.5",
"typescript": "^5.7.2"
"typescript": "^5.7.3"
}
}
692 changes: 571 additions & 121 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions templates/one-to-many/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# The base URL of the app. Required.
BASE_URL=""

# The database connection string. Required. https://orm.drizzle.team/docs/connect-overview
DATABASE_URL=""

# The API key for Resend. Required for magic links. If not set, disables magic link auth. https://resend.com/ https://authjs.dev/guides/configuring-resend
RESEND_KEY=""

# The email address to send magic links from. Required for magic links.
RESEND_EMAIL_FROM=""

# A secret used to sign cookies and to sign and verify JSON Web Tokens. See Auth.js docs on how to generate. Required in production. https://authjs.dev/getting-started/deployment#auth_secret
AUTH_SECRET=""

# The Client ID for your Google OAuth app. Required for social sign in. See Auth.js docs for set up. https://authjs.dev/getting-started/providers/google#setup
AUTH_GOOGLE_ID=""

# The Client Secret for your Google OAuth app. Required for social sign in. See Auth.js docs for set up. https://authjs.dev/getting-started/providers/google#setup
AUTH_GOOGLE_SECRET=""
6 changes: 6 additions & 0 deletions templates/one-to-many/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
"@typescript-eslint/no-empty-object-type": "off"
}
}
37 changes: 37 additions & 0 deletions templates/one-to-many/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
20 changes: 20 additions & 0 deletions templates/one-to-many/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# next-auth-template

![Built on next.js](https://img.shields.io/github/package-json/dependency-version/jakeisonline/next-auth-template/next?style=flat-square) ![Built on next-auth](https://img.shields.io/github/package-json/dependency-version/jakeisonline/next-auth-template/next-auth?style=flat-square) ![Built on drizzle-orm](https://img.shields.io/github/package-json/dependency-version/jakeisonline/next-auth-template/drizzle-orm?style=flat-square) ![Built on zod](https://img.shields.io/github/package-json/dependency-version/jakeisonline/next-auth-template/zod?style=flat-square)

This template gets you up a running with social sign in, magic links, database-backed sessions, and account creation and setup. It's a good starting point for your next project.

### [[View the demo]](https://next-auth-template-demo.vercel.app/)

<sup>_\* The demo database is reset every few hours_ </sup>

> [!NOTE]
> This template uses major dependencies that are not yet stable. It is not recommended for production use until `next-auth` and `drizzle-orm` are stable
# Documentation

Visit https://jakeisonline.com/playground/tools/next-auth-template for detailed documentation.

# License

Licensed under the [MIT license](./LICENSE).
20 changes: 20 additions & 0 deletions templates/one-to-many/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
10 changes: 10 additions & 0 deletions templates/one-to-many/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit"

export default defineConfig({
schema: "./src/db/schema",
out: "./src/db/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
})
4 changes: 4 additions & 0 deletions templates/one-to-many/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
59 changes: 59 additions & 0 deletions templates/one-to-many/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"name": "next-auth-template-one-to-many",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"db:migrate": "drizzle-kit migrate",
"db:generate": "drizzle-kit generate"
},
"dependencies": {
"@auth/drizzle-adapter": "^1.7.4",
"@neondatabase/serverless": "^0.10.3",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc",
"@types/uuid": "^10.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"drizzle-orm": "^0.38.3",
"lucide-react": "^0.451.0",
"next": "15.1.3",
"next-auth": "5.0.0-beta.25",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"react": "19.0.0-rc.1",
"react-dom": "19.0.0-rc.1",
"resend": "^4.0.1",
"tailwind-merge": "^2.5.3",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "^20",
"@types/pg": "^8.11.10",
"@types/react": "^18",
"@types/react-dom": "^18",
"drizzle-kit": "^0.30.1",
"eslint": "^8",
"eslint-config-next": "15.0.3",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
"tsx": "^4.19.1",
"typescript": "^5"
}
}
8 changes: 8 additions & 0 deletions templates/one-to-many/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};

export default config;
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 115 additions & 0 deletions templates/one-to-many/src/actions/account/do-account-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use server"

import { db } from "@/db"
import { accountsTable } from "@/db/schema/accounts"
import { z } from "zod"
import { ServerActionResponse } from "@/lib/types"
import { usersAccountsTable } from "@/db/schema/users_accounts"
import { withFormProtection } from "@/actions/action-middleware"

/**
* Server action to set up a new account for a authenticated user.
*
* @param prevState - Previous server action response state (unused but required for Next.js Server Actions)
* @param formData - Form data containing the account setup information
* @returns {Promise<ServerActionResponse>} Response object containing status and optional messages
*
* Protected by withActionProtection middleware which ensures:
* - User is authenticated
* - FormData is present
*
* The function performs the following:
* 1. Validates the account name
* 2. Creates a new account in the database
* 3. Links the account to the authenticated user as owner
*/

export const doAccountSetup = withFormProtection(
async (
prevState: ServerActionResponse | undefined,
formData?: FormData,
): Promise<ServerActionResponse> => {
const validatedAccountName = z
.string({
required_error: "required_error",
invalid_type_error: "invalid_type_error",
})
.trim()
.safeParse(formData!.get("account_name"))

if (!validatedAccountName.success) {
return {
status: "error",
messages: [
{
title: "Invalid account name",
body: "No account name was provided, or type is invalid",
},
],
}
}

try {
const createdAccount = await db
.insert(accountsTable)
.values({
name: validatedAccountName.data,
})
.returning()

if (!createdAccount[0].id) {
return {
status: "error",
messages: [
{
title: "Account creation failed",
body: "Failed to create account",
},
],
}
}

const createdUserAccount = await db
.insert(usersAccountsTable)
.values({
userId: formData!.get("sessionUserId") as string,
accountId: createdAccount[0].id,
role: "owner",
})
.returning()

if (!createdUserAccount[0].id) {
return {
status: "error",
messages: [
{
title: "Account setup failed",
body: "Failed to update user account",
},
],
}
}

return {
status: "success",
}
} catch (error) {
return {
status: "error",
messages: [
{
title: "Account setup failed",
body:
error instanceof Error
? error.message
: "An unknown error occurred",
},
],
}
}
},
{
requireAuth: true,
validateFormData: true,
},
)
Loading

0 comments on commit d5639d8

Please sign in to comment.