create-ej-app 8.0.0 → 10.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": "10.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,47 @@
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
+ export function useSafeActionQuery<TAction extends AnyAction>(
18
+ action: TAction,
19
+ input: ActionInput<TAction>,
20
+ options?: Omit<UseQueryOptions<ActionData<TAction>>, 'queryKey' | 'queryFn'>
21
+ ) {
22
+ return useQuery({
23
+ queryKey: [`${action.name}`, input],
24
+ queryFn: async () => {
25
+ const result = await action(input)
26
+ if (result?.serverError) throw new Error(result.serverError)
27
+ if (result?.validationErrors) throw new Error('Validation failed')
28
+ return result?.data
29
+ },
30
+ ...options
31
+ })
32
+ }
33
+
34
+ export function useSafeActionMutation<TAction extends AnyAction>(
35
+ action: TAction,
36
+ options?: UseMutationOptions<ActionData<TAction>, Error, ActionInput<TAction>>
37
+ ) {
38
+ return useMutation({
39
+ mutationFn: async (input: ActionInput<TAction>) => {
40
+ const result = await action(input)
41
+ if (result?.serverError) throw new Error(result.serverError)
42
+ if (result?.validationErrors) throw new Error('Validation failed')
43
+ return result?.data
44
+ },
45
+ ...options
46
+ })
47
+ }
@@ -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
  })