create-ej-app 8.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ej-app",
3
- "version": "8.0.0",
3
+ "version": "11.0.0",
4
4
  "description": "Bootstrap a next.js app with my preferred setup",
5
5
  "main": "cli.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  "enquirer": "^2.4.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/node": "^25.0.1",
38
+ "@types/node": "^25.0.3",
39
39
  "typescript": "^5.9.3"
40
40
  }
41
41
  }
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ .next
3
+ out
4
+ build
5
+ next-env.d.ts
6
+ .DS_Store
7
+ .env
@@ -5,9 +5,10 @@
5
5
  "private": true,
6
6
  "scripts": {
7
7
  "dev": "next dev",
8
- "build": "next build",
9
- "analyze": "ANALYZE=true next build --webpack",
10
- "start": "next start",
8
+ "build": "bun --bun next build",
9
+ "build:docker": "docker build -t my-app .",
10
+ "analyze": "ANALYZE=true bun --bun next build --webpack",
11
+ "start": "bun --bun next start",
11
12
  "lint": "eslint",
12
13
  "format": "prettier --write \"./src/**/*.{js,cjs,mjs,jsx,ts,tsx}\"",
13
14
  "db:push": "drizzle-kit push",
@@ -16,19 +17,20 @@
16
17
  },
17
18
  "dependencies": {
18
19
  "@hookform/resolvers": "5.2.2",
19
- "@next/third-parties": "16.0.10",
20
+ "@next/third-parties": "16.1.0",
20
21
  "@paralleldrive/cuid2": "3.0.4",
21
22
  "@radix-ui/react-label": "2.1.8",
22
23
  "@radix-ui/react-separator": "1.1.8",
23
24
  "@radix-ui/react-slot": "1.2.4",
24
- "@t3-oss/env-nextjs": "0.13.8",
25
+ "@t3-oss/env-nextjs": "0.13.10",
25
26
  "@tanstack/react-query": "5.90.12",
26
- "better-auth": "1.4.6",
27
+ "better-auth": "1.4.7",
27
28
  "class-variance-authority": "0.7.1",
28
29
  "clsx": "2.1.1",
29
30
  "drizzle-orm": "0.45.1",
30
- "lucide-react": "0.561.0",
31
- "next": "16.0.10",
31
+ "fakefilter": "0.1.1401",
32
+ "lucide-react": "0.562.0",
33
+ "next": "16.1.0",
32
34
  "next-safe-action": "8.0.11",
33
35
  "next-themes": "0.4.6",
34
36
  "nextjs-toploader": "3.9.17",
@@ -36,24 +38,24 @@
36
38
  "postgres": "3.4.7",
37
39
  "react": "19.2.3",
38
40
  "react-dom": "19.2.3",
39
- "react-hook-form": "7.68.0",
41
+ "react-hook-form": "7.69.0",
40
42
  "sonner": "2.0.7",
41
43
  "tailwind-merge": "3.4.0",
42
44
  "tw-animate-css": "1.4.0",
43
- "workflow": "4.0.1-beta.29",
44
- "zod": "4.1.13"
45
+ "workflow": "4.0.1-beta.35",
46
+ "zod": "4.2.1"
45
47
  },
46
48
  "devDependencies": {
47
- "@next/bundle-analyzer": "16.0.10",
49
+ "@next/bundle-analyzer": "16.1.0",
48
50
  "@tailwindcss/postcss": "4.1.18",
49
- "@types/bun": "1.3.4",
51
+ "@types/bun": "1.3.5",
50
52
  "@types/nodemailer": "7.0.4",
51
53
  "@types/react": "19.2.7",
52
54
  "@types/react-dom": "19.2.3",
53
55
  "babel-plugin-react-compiler": "1.0.0",
54
56
  "drizzle-kit": "0.31.8",
55
57
  "eslint": "9.39.2",
56
- "eslint-config-next": "16.0.10",
58
+ "eslint-config-next": "16.1.0",
57
59
  "prettier": "3.7.4",
58
60
  "prettier-plugin-organize-imports": "4.3.0",
59
61
  "prettier-plugin-tailwindcss": "0.7.2",
@@ -9,7 +9,7 @@ export default async function Layout({
9
9
  const authSession = await getAuthSession()
10
10
 
11
11
  if (!authSession) {
12
- redirect('/auth')
12
+ redirect('/login')
13
13
  }
14
14
 
15
15
  return (
@@ -16,7 +16,7 @@ import {
16
16
  } from '~/components/ui/form'
17
17
  import { Input } from '~/components/ui/input'
18
18
  import { signIn } from '~/lib/auth-client'
19
- import { toastSuccessMessage } from '~/lib/toast-message'
19
+ import { toastErrorMessage, toastSuccessMessage } from '~/lib/toast-message'
20
20
 
21
21
  const emailLoginFormSchema = z.object({
22
22
  email: z.email()
@@ -49,6 +49,14 @@ export const EmailLoginForm = () => {
49
49
  toastSuccessMessage('Magic link sent! Check your email.')
50
50
  } catch (err) {
51
51
  console.error('Error sending magic link:', err)
52
+ if (
53
+ typeof err === 'object' &&
54
+ err &&
55
+ 'message' in err &&
56
+ typeof err.message === 'string'
57
+ )
58
+ toastErrorMessage(err.message)
59
+ else toastErrorMessage('Something went wrong. Please try again.')
52
60
  } finally {
53
61
  setIsLoading(false)
54
62
  }
@@ -3,8 +3,8 @@ import { siteConfig } from '~/lib/siteConfig'
3
3
 
4
4
  export const SiteFooter = () => {
5
5
  return (
6
- <footer className="container border-t">
7
- <div className="text-muted-foreground flex flex-col items-center justify-between gap-4 text-sm sm:flex-row">
6
+ <footer className="border-t">
7
+ <div className="text-muted-foreground container flex flex-col items-center justify-between gap-4 py-6 text-sm sm:flex-row">
8
8
  <p>
9
9
  &copy; {new Date().getUTCFullYear()} {siteConfig.name}. All rights
10
10
  reserved.
@@ -1,7 +1,13 @@
1
1
  import { createId } from '@paralleldrive/cuid2'
2
- import { betterAuth } from 'better-auth'
2
+ import { APIError, betterAuth } from 'better-auth'
3
3
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
4
- import { admin, lastLoginMethod, magicLink } from 'better-auth/plugins'
4
+ import {
5
+ admin,
6
+ createAuthMiddleware,
7
+ lastLoginMethod,
8
+ magicLink
9
+ } from 'better-auth/plugins'
10
+ import { isFakeEmail } from 'fakefilter'
5
11
  import { headers } from 'next/headers'
6
12
  import { cache } from 'react'
7
13
  import { db } from '~/db'
@@ -57,7 +63,20 @@ export const auth = betterAuth({
57
63
  }),
58
64
  admin(),
59
65
  lastLoginMethod()
60
- ]
66
+ ],
67
+ hooks: {
68
+ before: createAuthMiddleware(async ctx => {
69
+ if (
70
+ ctx.path === '/sign-in/magic-link' &&
71
+ ctx.body?.email &&
72
+ isFakeEmail(ctx.body.email)
73
+ ) {
74
+ throw new APIError('BAD_REQUEST', {
75
+ message: 'Please use a valid email address.'
76
+ })
77
+ }
78
+ })
79
+ }
61
80
  })
62
81
 
63
82
  export const getAuthSession = cache(async () => {
@@ -0,0 +1,75 @@
1
+ 'use client'
2
+
3
+ import {
4
+ useMutation,
5
+ useQuery,
6
+ type UseMutationOptions,
7
+ type UseQueryOptions
8
+ } from '@tanstack/react-query'
9
+ import type { SafeActionResult } from 'next-safe-action'
10
+
11
+ type AnyAction = (
12
+ ...args: any[]
13
+ ) => Promise<SafeActionResult<any, any, any, any, any>>
14
+ type ActionInput<T extends AnyAction> = Parameters<T>[0]
15
+ type ActionData<T extends AnyAction> = Awaited<ReturnType<T>>['data']
16
+
17
+ class SafeActionError extends Error {
18
+ constructor(
19
+ public message: string,
20
+ public validationErrors?: Record<string, string[] | undefined>
21
+ ) {
22
+ super(message)
23
+ }
24
+ }
25
+
26
+ export function useSafeActionQuery<TAction extends AnyAction>(
27
+ key: string,
28
+ action: TAction,
29
+ input: ActionInput<TAction>,
30
+ options?: Omit<UseQueryOptions<ActionData<TAction>>, 'queryKey' | 'queryFn'>
31
+ ) {
32
+ return useQuery({
33
+ queryKey: [key, input],
34
+ queryFn: async () => {
35
+ const result = await action(input)
36
+
37
+ if (result?.serverError) {
38
+ throw new SafeActionError(result.serverError)
39
+ }
40
+
41
+ if (result?.validationErrors) {
42
+ throw new SafeActionError('Validation failed', result.validationErrors)
43
+ }
44
+
45
+ return result?.data
46
+ },
47
+ ...options
48
+ })
49
+ }
50
+
51
+ export function useSafeActionMutation<TAction extends AnyAction>(
52
+ action: TAction,
53
+ options?: UseMutationOptions<
54
+ ActionData<TAction>,
55
+ SafeActionError,
56
+ ActionInput<TAction>
57
+ >
58
+ ) {
59
+ return useMutation({
60
+ mutationFn: async (input: ActionInput<TAction>) => {
61
+ const result = await action(input)
62
+
63
+ if (result?.serverError) {
64
+ throw new SafeActionError(result.serverError)
65
+ }
66
+
67
+ if (result?.validationErrors) {
68
+ throw new SafeActionError('Validation failed', result.validationErrors)
69
+ }
70
+
71
+ return result?.data
72
+ },
73
+ ...options
74
+ })
75
+ }
@@ -8,8 +8,8 @@ export const toastSuccessMessage = (message: string) =>
8
8
  position: 'top-center'
9
9
  })
10
10
 
11
- export const toastErrorMessage = () =>
12
- toast.error('Something went wrong. Please try again later.', {
11
+ export const toastErrorMessage = (message?: string) =>
12
+ toast.error(message || 'Something went wrong. Please try again later.', {
13
13
  richColors: true,
14
14
  position: 'top-center'
15
15
  })