wexts 2.0.6 → 2.0.8

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.
Files changed (110) hide show
  1. package/dist/chunk-63MTCWU2.mjs +361 -0
  2. package/dist/chunk-63MTCWU2.mjs.map +1 -0
  3. package/dist/chunk-67IJ6H4J.mjs +44 -0
  4. package/dist/chunk-67IJ6H4J.mjs.map +1 -0
  5. package/dist/chunk-7NSRDJ5C.mjs +1 -0
  6. package/dist/chunk-7NSRDJ5C.mjs.map +1 -0
  7. package/dist/chunk-ASDXAK6G.js +44 -0
  8. package/dist/chunk-ASDXAK6G.js.map +1 -0
  9. package/dist/chunk-CKZ4VSCB.mjs +18 -0
  10. package/dist/chunk-CKZ4VSCB.mjs.map +1 -0
  11. package/dist/chunk-DW6GOKMF.js +57 -0
  12. package/dist/chunk-DW6GOKMF.js.map +1 -0
  13. package/dist/chunk-GKVPGKAH.js +66 -0
  14. package/dist/chunk-GKVPGKAH.js.map +1 -0
  15. package/dist/chunk-HSFLZUJN.mjs +57 -0
  16. package/dist/chunk-HSFLZUJN.mjs.map +1 -0
  17. package/dist/chunk-HU63F22V.js +361 -0
  18. package/dist/chunk-HU63F22V.js.map +1 -0
  19. package/dist/chunk-JMBD6DOP.js +225 -0
  20. package/dist/chunk-JMBD6DOP.js.map +1 -0
  21. package/dist/chunk-K7EIJSYQ.js +1 -0
  22. package/dist/chunk-K7EIJSYQ.js.map +1 -0
  23. package/dist/chunk-OTBYRUBE.mjs +225 -0
  24. package/dist/chunk-OTBYRUBE.mjs.map +1 -0
  25. package/dist/chunk-WMHVXEYQ.mjs +66 -0
  26. package/dist/chunk-WMHVXEYQ.mjs.map +1 -0
  27. package/dist/cli/index.js +91 -43
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cli/index.mjs +62 -12
  30. package/dist/cli/index.mjs.map +1 -1
  31. package/dist/client/index.js +2 -2
  32. package/dist/client/index.mjs +2 -2
  33. package/dist/codegen/index.d.mts +1 -0
  34. package/dist/codegen/index.d.ts +1 -0
  35. package/dist/codegen/index.js +13 -0
  36. package/dist/codegen/index.js.map +1 -0
  37. package/dist/codegen/index.mjs +13 -0
  38. package/dist/codegen/index.mjs.map +1 -0
  39. package/dist/dev-server/index.d.mts +1 -0
  40. package/dist/dev-server/index.d.ts +1 -0
  41. package/dist/dev-server/index.js +13 -0
  42. package/dist/dev-server/index.js.map +1 -0
  43. package/dist/dev-server/index.mjs +13 -0
  44. package/dist/dev-server/index.mjs.map +1 -0
  45. package/dist/index-SjUaHgFr.d.mts +75 -0
  46. package/dist/index-SjUaHgFr.d.ts +75 -0
  47. package/dist/index-tFGPFVfQ.d.mts +67 -0
  48. package/dist/index-tFGPFVfQ.d.ts +67 -0
  49. package/dist/index.d.mts +83 -164
  50. package/dist/index.d.ts +83 -164
  51. package/dist/index.js +89 -22
  52. package/dist/index.js.map +1 -1
  53. package/dist/index.mjs +76 -9
  54. package/dist/index.mjs.map +1 -1
  55. package/dist/nest/index.js +2 -2
  56. package/dist/nest/index.mjs +2 -2
  57. package/dist/next/index.d.mts +61 -3
  58. package/dist/next/index.d.ts +61 -3
  59. package/dist/next/index.js +140 -7
  60. package/dist/next/index.js.map +1 -1
  61. package/dist/next/index.mjs +102 -7
  62. package/dist/next/index.mjs.map +1 -1
  63. package/dist/types/index.js +1 -1
  64. package/dist/types/index.mjs +2 -1
  65. package/package.json +1 -1
  66. package/templates/nestjs-api/package-lock.json +5623 -0
  67. package/templates/nestjs-api/package.json +21 -19
  68. package/templates/nestjs-api/prisma/migrations/20251123205437_init/migration.sql +24 -0
  69. package/templates/nestjs-api/prisma/migrations/migration_lock.toml +3 -0
  70. package/templates/nestjs-api/src/auth/auth.controller.ts +5 -5
  71. package/templates/nestjs-api/src/main.ts +1 -1
  72. package/templates/nestjs-api/src/todos/todos.controller.ts +7 -7
  73. package/templates/nestjs-api/src/users/users.controller.ts +3 -3
  74. package/templates/nestjs-api/tsconfig.json +20 -1
  75. package/templates/nextjs-web/app/actions/auth.ts +79 -0
  76. package/templates/nextjs-web/app/dashboard/error.tsx +39 -0
  77. package/templates/nextjs-web/app/dashboard/loading.tsx +14 -0
  78. package/templates/nextjs-web/app/dashboard/page.tsx +2 -172
  79. package/templates/nextjs-web/app/globals.css +80 -15
  80. package/templates/nextjs-web/app/layout.tsx +7 -5
  81. package/templates/nextjs-web/app/login/page.tsx +2 -104
  82. package/templates/nextjs-web/app/page.tsx +1 -1
  83. package/templates/nextjs-web/app/register/page.tsx +2 -127
  84. package/templates/nextjs-web/components/ui/button.tsx +56 -0
  85. package/templates/nextjs-web/components/ui/card.tsx +79 -0
  86. package/templates/nextjs-web/components/ui/input.tsx +25 -0
  87. package/templates/nextjs-web/components/ui/label.tsx +24 -0
  88. package/templates/nextjs-web/features/auth/LoginForm.tsx +140 -0
  89. package/templates/nextjs-web/features/auth/RegisterForm.tsx +159 -0
  90. package/templates/nextjs-web/features/auth/api.ts +35 -0
  91. package/templates/nextjs-web/features/auth/index.ts +3 -0
  92. package/templates/nextjs-web/features/dashboard/DashboardView.tsx +204 -0
  93. package/templates/nextjs-web/features/dashboard/api.ts +9 -0
  94. package/templates/nextjs-web/features/dashboard/components.tsx +74 -0
  95. package/templates/nextjs-web/features/dashboard/index.ts +3 -0
  96. package/templates/nextjs-web/hooks/index.ts +4 -0
  97. package/templates/nextjs-web/lib/api-client.ts +89 -0
  98. package/templates/nextjs-web/lib/axios-global-config.ts +17 -0
  99. package/templates/nextjs-web/lib/utils.ts +6 -0
  100. package/templates/nextjs-web/lib/wexts-client.ts +4 -0
  101. package/templates/nextjs-web/next-env.d.ts +6 -0
  102. package/templates/nextjs-web/next.config.ts +20 -0
  103. package/templates/nextjs-web/package-lock.json +3254 -0
  104. package/templates/nextjs-web/package.json +23 -14
  105. package/templates/nextjs-web/postcss.config.js +6 -0
  106. package/templates/nextjs-web/tailwind.config.ts +55 -1
  107. package/templates/nextjs-web/tsconfig.json +41 -39
  108. package/templates/nestjs-api/.env.example +0 -4
  109. package/templates/nextjs-web/next.config.mjs +0 -4
  110. /package/templates/nextjs-web/{.env.local.example → .env} +0 -0
@@ -1,13 +1,14 @@
1
1
  import type { Metadata } from 'next';
2
2
  import { Inter } from 'next/font/google';
3
- import { FusionProvider } from 'wexts/next';
3
+ import { WextsProvider } from '@/lib/wexts-client';
4
+ import { Toaster } from 'react-hot-toast';
4
5
  import './globals.css';
5
6
 
6
7
  const inter = Inter({ subsets: ['latin'] });
7
8
 
8
9
  export const metadata: Metadata = {
9
- title: 'Fusion App',
10
- description: 'Built with wexts',
10
+ title: 'wexts Web App',
11
+ description: 'Modern web application built with wexts',
11
12
  };
12
13
 
13
14
  export default function RootLayout({
@@ -18,9 +19,10 @@ export default function RootLayout({
18
19
  return (
19
20
  <html lang="en">
20
21
  <body className={inter.className}>
21
- <FusionProvider baseUrl={process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5050'}>
22
+ <WextsProvider baseUrl={process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5050'}>
22
23
  {children}
23
- </FusionProvider>
24
+ <Toaster position="top-center" />
25
+ </WextsProvider>
24
26
  </body>
25
27
  </html>
26
28
  );
@@ -1,107 +1,5 @@
1
- 'use client';
2
-
3
- import { useState, FormEvent } from 'react';
4
- import { useAuth } from 'wexts/next';
5
- import { useRouter } from 'next/navigation';
6
- import Link from 'next/link';
1
+ import { LoginForm } from '@/features/auth';
7
2
 
8
3
  export default function LoginPage() {
9
- const [email, setEmail] = useState('');
10
- const [password, setPassword] = useState('');
11
- const [error, setError] = useState('');
12
- const [loading, setLoading] = useState(false);
13
-
14
- const { login } = useAuth();
15
- const router = useRouter();
16
-
17
- const handleSubmit = async (e: FormEvent) => {
18
- e.preventDefault();
19
- setError('');
20
- setLoading(true);
21
-
22
- try {
23
- await login(email, password);
24
- router.push('/dashboard');
25
- } catch (err: any) {
26
- setError(err.message || 'Login failed');
27
- } finally {
28
- setLoading(false);
29
- }
30
- };
31
-
32
- return (
33
- <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-violet-50 to-purple-100 dark:from-gray-900 dark:to-gray-800 px-4">
34
- <div className="max-w-md w-full space-y-8 bg-white dark:bg-gray-800 p-10 rounded-2xl shadow-xl">
35
- <div>
36
- <h2 className="text-center text-4xl font-bold text-gray-900 dark:text-white">
37
- Welcome Back
38
- </h2>
39
- <p className="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
40
- Sign in to your account
41
- </p>
42
- </div>
43
-
44
- <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
45
- {error && (
46
- <div className="rounded-lg bg-red-50 dark:bg-red-900/20 p-4 text-sm text-red-600 dark:text-red-400">
47
- {error}
48
- </div>
49
- )}
50
-
51
- <div className="space-y-4">
52
- <div>
53
- <label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
54
- Email
55
- </label>
56
- <input
57
- id="email"
58
- name="email"
59
- type="email"
60
- autoComplete="email"
61
- required
62
- value={email}
63
- onChange={(e) => setEmail(e.target.value)}
64
- className="appearance-none relative block w-full px-4 py-3 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-transparent bg-white dark:bg-gray-700 transition"
65
- placeholder="you@example.com"
66
- />
67
- </div>
68
-
69
- <div>
70
- <label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
71
- Password
72
- </label>
73
- <input
74
- id="password"
75
- name="password"
76
- type="password"
77
- autoComplete="current-password"
78
- required
79
- value={password}
80
- onChange={(e) => setPassword(e.target.value)}
81
- className="appearance-none relative block w-full px-4 py-3 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-transparent bg-white dark:bg-gray-700 transition"
82
- placeholder="••••••••"
83
- />
84
- </div>
85
- </div>
86
-
87
- <div>
88
- <button
89
- type="submit"
90
- disabled={loading}
91
- className="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-semibold rounded-lg text-white bg-violet-600 hover:bg-violet-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-violet-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
92
- >
93
- {loading ? 'Signing in...' : 'Sign in'}
94
- </button>
95
- </div>
96
-
97
- <div className="text-center text-sm">
98
- <span className="text-gray-600 dark:text-gray-400">Don't have an account? </span>
99
- <Link href="/register" className="font-medium text-violet-600 hover:text-violet-500 dark:text-violet-400">
100
- Sign up
101
- </Link>
102
- </div>
103
- </form>
104
- </div>
105
- </div>
106
- );
4
+ return <LoginForm />;
107
5
  }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useAuth } from 'wexts/next';
3
+ import { useAuth } from '@/lib/wexts-client';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { useEffect } from 'react';
6
6
 
@@ -1,130 +1,5 @@
1
- 'use client';
2
-
3
- import { useState, FormEvent } from 'react';
4
- import { useFusion } from 'wexts/next';
5
- import { useRouter } from 'next/navigation';
6
- import Link from 'next/link';
1
+ import { RegisterForm } from '@/features/auth';
7
2
 
8
3
  export default function RegisterPage() {
9
- const [email, setEmail] = useState('');
10
- const [password, setPassword] = useState('');
11
- const [name, setName] = useState('');
12
- const [error, setError] = useState('');
13
- const [loading, setLoading] = useState(false);
14
-
15
- const { client } = useFusion();
16
- const router = useRouter();
17
-
18
- const handleSubmit = async (e: FormEvent) => {
19
- e.preventDefault();
20
- setError('');
21
- setLoading(true);
22
-
23
- try {
24
- const response = await client.post<{ token: string; user: any }>('/auth/register', {
25
- email,
26
- password,
27
- name,
28
- });
29
-
30
- localStorage.setItem('fusion_token', response.token);
31
- router.push('/dashboard');
32
- } catch (err: any) {
33
- setError(err.message || 'Registration failed');
34
- } finally {
35
- setLoading(false);
36
- }
37
- };
38
-
39
- return (
40
- <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-violet-50 to-purple-100 dark:from-gray-900 dark:to-gray-800 px-4">
41
- <div className="max-w-md w-full space-y-8 bg-white dark:bg-gray-800 p-10 rounded-2xl shadow-xl">
42
- <div>
43
- <h2 className="text-center text-4xl font-bold text-gray-900 dark:text-white">
44
- Create Account
45
- </h2>
46
- <p className="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
47
- Get started with Fusion
48
- </p>
49
- </div>
50
-
51
- <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
52
- {error && (
53
- <div className="rounded-lg bg-red-50 dark:bg-red-900/20 p-4 text-sm text-red-600 dark:text-red-400">
54
- {error}
55
- </div>
56
- )}
57
-
58
- <div className="space-y-4">
59
- <div>
60
- <label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
61
- Name (optional)
62
- </label>
63
- <input
64
- id="name"
65
- name="name"
66
- type="text"
67
- value={name}
68
- onChange={(e) => setName(e.target.value)}
69
- className="appearance-none relative block w-full px-4 py-3 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-transparent bg-white dark:bg-gray-700 transition"
70
- placeholder="John Doe"
71
- />
72
- </div>
73
-
74
- <div>
75
- <label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
76
- Email
77
- </label>
78
- <input
79
- id="email"
80
- name="email"
81
- type="email"
82
- autoComplete="email"
83
- required
84
- value={email}
85
- onChange={(e) => setEmail(e.target.value)}
86
- className="appearance-none relative block w-full px-4 py-3 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-transparent bg-white dark:bg-gray-700 transition"
87
- placeholder="you@example.com"
88
- />
89
- </div>
90
-
91
- <div>
92
- <label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
93
- Password
94
- </label>
95
- <input
96
- id="password"
97
- name="password"
98
- type="password"
99
- autoComplete="new-password"
100
- required
101
- value={password}
102
- onChange={(e) => setPassword(e.target.value)}
103
- className="appearance-none relative block w-full px-4 py-3 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-transparent bg-white dark:bg-gray-700 transition"
104
- placeholder="••••••••"
105
- />
106
- <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">Minimum 6 characters</p>
107
- </div>
108
- </div>
109
-
110
- <div>
111
- <button
112
- type="submit"
113
- disabled={loading}
114
- className="group relative w-full flex justify-center py-3 px-4 border border-transparent text-sm font-semibold rounded-lg text-white bg-violet-600 hover:bg-violet-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-violet-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all"
115
- >
116
- {loading ? 'Creating account...' : 'Sign up'}
117
- </button>
118
- </div>
119
-
120
- <div className="text-center text-sm">
121
- <span className="text-gray-600 dark:text-gray-400">Already have an account? </span>
122
- <Link href="/login" className="font-medium text-violet-600 hover:text-violet-500 dark:text-violet-400">
123
- Sign in
124
- </Link>
125
- </div>
126
- </form>
127
- </div>
128
- </div>
129
- );
4
+ return <RegisterForm />;
130
5
  }
@@ -0,0 +1,56 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
@@ -0,0 +1,79 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,25 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> { }
7
+
8
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ )
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
+ VariantProps<typeof labelVariants>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={cn(labelVariants(), className)}
19
+ {...props}
20
+ />
21
+ ))
22
+ Label.displayName = LabelPrimitive.Root.displayName
23
+
24
+ export { Label }
@@ -0,0 +1,140 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { login } from './api';
6
+ import { Button } from '@/components/ui/button';
7
+ import { Input } from '@/components/ui/input';
8
+ import { Label } from '@/components/ui/label';
9
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
10
+ import Link from 'next/link';
11
+ import { Zap, Mail, Lock, ArrowRight, Loader2 } from 'lucide-react';
12
+
13
+ export function LoginForm() {
14
+ const router = useRouter();
15
+ const [loading, setLoading] = useState(false);
16
+ const [error, setError] = useState('');
17
+
18
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
19
+ e.preventDefault();
20
+ setLoading(true);
21
+ setError('');
22
+
23
+ const formData = new FormData(e.currentTarget);
24
+ const data = Object.fromEntries(formData.entries());
25
+
26
+ try {
27
+ await login(data);
28
+ router.push('/dashboard');
29
+ } catch (err: any) {
30
+ console.error(err);
31
+ setError(err.response?.data?.message || 'Login failed. Please check your credentials.');
32
+ } finally {
33
+ setLoading(false);
34
+ }
35
+ };
36
+
37
+ return (
38
+ <div className="min-h-screen flex items-center justify-center relative overflow-hidden bg-background">
39
+ {/* Animated Background Elements */}
40
+ <div className="absolute inset-0 overflow-hidden pointer-events-none">
41
+ <div className="absolute -top-[20%] -left-[10%] w-[50%] h-[50%] rounded-full bg-primary/20 blur-[100px] animate-float" />
42
+ <div className="absolute top-[40%] -right-[10%] w-[40%] h-[40%] rounded-full bg-indigo-500/20 blur-[100px] animate-float" style={{ animationDelay: '2s' }} />
43
+ <div className="absolute -bottom-[10%] left-[20%] w-[30%] h-[30%] rounded-full bg-violet-500/20 blur-[100px] animate-float" style={{ animationDelay: '4s' }} />
44
+ </div>
45
+
46
+ <div className="w-full max-w-md p-8 relative z-10">
47
+ <Card className="glass border-white/20 shadow-2xl backdrop-blur-xl">
48
+ <CardHeader className="text-center">
49
+ <div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary/10 mb-6 mx-auto text-primary">
50
+ <Zap className="w-8 h-8" />
51
+ </div>
52
+ <CardTitle className="text-3xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary to-indigo-600">
53
+ Welcome Back
54
+ </CardTitle>
55
+ <CardDescription>
56
+ Sign in to continue to wexts
57
+ </CardDescription>
58
+ </CardHeader>
59
+ <CardContent>
60
+ <form onSubmit={handleSubmit} className="space-y-6">
61
+ {error && (
62
+ <div className="p-4 rounded-xl bg-destructive/10 text-destructive text-sm font-medium border border-destructive/20 animate-pulse-slow flex items-center gap-2">
63
+ <span className="text-lg">⚠️</span>
64
+ {error}
65
+ </div>
66
+ )}
67
+
68
+ <div className="space-y-4">
69
+ <div className="space-y-2">
70
+ <Label htmlFor="email">Email Address</Label>
71
+ <div className="relative">
72
+ <Mail className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
73
+ <Input
74
+ id="email"
75
+ name="email"
76
+ type="email"
77
+ placeholder="name@example.com"
78
+ required
79
+ className="pl-10 bg-secondary/50 border-transparent focus:border-primary/50 focus:bg-background transition-all"
80
+ />
81
+ </div>
82
+ </div>
83
+
84
+ <div className="space-y-2">
85
+ <div className="flex items-center justify-between">
86
+ <Label htmlFor="password">Password</Label>
87
+ <Link href="#" className="text-xs font-medium text-primary hover:text-primary/80 transition-colors">
88
+ Forgot password?
89
+ </Link>
90
+ </div>
91
+ <div className="relative">
92
+ <Lock className="absolute left-3 top-3 w-4 h-4 text-muted-foreground" />
93
+ <Input
94
+ id="password"
95
+ name="password"
96
+ type="password"
97
+ placeholder="••••••••"
98
+ required
99
+ className="pl-10 bg-secondary/50 border-transparent focus:border-primary/50 focus:bg-background transition-all"
100
+ />
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <Button
106
+ type="submit"
107
+ disabled={loading}
108
+ className="w-full bg-gradient-to-r from-primary to-indigo-600 hover:from-primary/90 hover:to-indigo-600/90 shadow-lg shadow-primary/25 group"
109
+ >
110
+ {loading ? (
111
+ <>
112
+ <Loader2 className="w-4 h-4 mr-2 animate-spin" />
113
+ Signing in...
114
+ </>
115
+ ) : (
116
+ <>
117
+ Sign in
118
+ <ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
119
+ </>
120
+ )}
121
+ </Button>
122
+ </form>
123
+ </CardContent>
124
+ <CardFooter className="justify-center">
125
+ <div className="text-center text-sm text-muted-foreground">
126
+ Don't have an account?{' '}
127
+ <Link href="/register" className="font-semibold text-primary hover:text-primary/80 transition-colors">
128
+ Create account
129
+ </Link>
130
+ </div>
131
+ </CardFooter>
132
+ </Card>
133
+
134
+ <p className="text-center mt-8 text-xs text-muted-foreground/60">
135
+ &copy; 2025 wexts Inc. All rights reserved.
136
+ </p>
137
+ </div>
138
+ </div>
139
+ );
140
+ }