@roboticela/devkit 3.0.0 → 4.1.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.
Files changed (40) hide show
  1. package/README.md +12 -0
  2. package/dist/commands/add.js +103 -4
  3. package/dist/commands/eject.d.ts +1 -0
  4. package/dist/commands/eject.js +60 -0
  5. package/dist/index.js +10 -19
  6. package/dist/lib/bundled-registry.d.ts +18 -0
  7. package/dist/lib/bundled-registry.js +82 -0
  8. package/dist/lib/component-entry.d.ts +16 -0
  9. package/dist/lib/component-entry.js +1 -0
  10. package/dist/lib/config.d.ts +2 -0
  11. package/dist/lib/config.js +3 -1
  12. package/dist/lib/installer.js +9 -7
  13. package/dist/lib/registry.d.ts +2 -16
  14. package/dist/lib/registry.js +10 -0
  15. package/package.json +4 -2
  16. package/registry/components/auth/component.json +29 -0
  17. package/registry/components/auth/nextjs-compact/v1/files/app/api/auth/[...route]/route.ts +140 -0
  18. package/registry/components/auth/nextjs-compact/v1/files/app/auth/forgot-password/page.tsx +150 -0
  19. package/registry/components/auth/nextjs-compact/v1/files/app/auth/login/page.tsx +45 -0
  20. package/registry/components/auth/nextjs-compact/v1/files/app/auth/register/page.tsx +45 -0
  21. package/registry/components/auth/nextjs-compact/v1/files/app/auth/reset-password/page.tsx +139 -0
  22. package/registry/components/auth/nextjs-compact/v1/files/components/auth/AuthProvider.tsx +89 -0
  23. package/registry/components/auth/nextjs-compact/v1/files/components/auth/LoginForm.tsx +123 -0
  24. package/registry/components/auth/nextjs-compact/v1/files/components/auth/RegisterForm.tsx +106 -0
  25. package/registry/components/auth/nextjs-compact/v1/files/lib/auth/authService.ts +43 -0
  26. package/registry/components/auth/nextjs-compact/v1/manifest.json +37 -0
  27. package/registry/components/auth/vite-express-tauri/v1/files/server/middleware/requireAuth.ts +14 -0
  28. package/registry/components/auth/vite-express-tauri/v1/files/server/routes/auth.ts +83 -0
  29. package/registry/components/auth/vite-express-tauri/v1/files/server/services/jwtService.ts +19 -0
  30. package/registry/components/auth/vite-express-tauri/v1/files/src/contexts/AuthContext.tsx +72 -0
  31. package/registry/components/auth/vite-express-tauri/v1/manifest.json +47 -0
  32. package/registry/components/hero-section/component.json +28 -0
  33. package/registry/components/hero-section/nextjs-compact/v1/variants/centered/files/components/hero/HeroSection.tsx +97 -0
  34. package/registry/components/hero-section/nextjs-compact/v1/variants/centered/manifest.json +17 -0
  35. package/registry/components/hero-section/nextjs-compact/v1/variants/gradient-mesh/files/components/hero/HeroSection.tsx +146 -0
  36. package/registry/components/hero-section/nextjs-compact/v1/variants/gradient-mesh/manifest.json +17 -0
  37. package/registry/components/hero-section/nextjs-compact/v1/variants/split-image/files/components/hero/HeroSection.tsx +110 -0
  38. package/registry/components/hero-section/nextjs-compact/v1/variants/split-image/manifest.json +17 -0
  39. package/registry/components/registry.json +31 -0
  40. package/schemas/devkit-config.json +63 -0
@@ -0,0 +1,123 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useAuth } from "./AuthProvider";
6
+
7
+ export function LoginForm() {
8
+ const { login } = useAuth();
9
+ const router = useRouter();
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [error, setError] = useState("");
13
+ const [loading, setLoading] = useState(false);
14
+
15
+ async function handleSubmit(e: React.FormEvent) {
16
+ e.preventDefault();
17
+ setError("");
18
+ setLoading(true);
19
+ try {
20
+ await login(email, password);
21
+ router.push("/");
22
+ } catch (err) {
23
+ setError(err instanceof Error ? err.message : "Login failed");
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ }
28
+
29
+ return (
30
+ <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "var(--space-4)" }}>
31
+ {error && (
32
+ <div
33
+ role="alert"
34
+ style={{
35
+ padding: "var(--space-3) var(--space-4)",
36
+ borderRadius: "var(--radius-md)",
37
+ background: "var(--color-error-subtle)",
38
+ color: "var(--color-error-text)",
39
+ fontSize: "var(--text-sm)",
40
+ }}
41
+ >
42
+ {error}
43
+ </div>
44
+ )}
45
+
46
+ <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
47
+ <label htmlFor="email" style={{ fontSize: "var(--text-sm)", fontWeight: "var(--weight-medium)", color: "var(--color-text)" }}>
48
+ Email
49
+ </label>
50
+ <input
51
+ id="email"
52
+ type="email"
53
+ required
54
+ autoComplete="email"
55
+ value={email}
56
+ onChange={(e) => setEmail(e.target.value)}
57
+ style={{
58
+ padding: "var(--space-3) var(--space-4)",
59
+ borderRadius: "var(--radius-md)",
60
+ border: "1px solid var(--color-border)",
61
+ background: "var(--color-bg)",
62
+ color: "var(--color-text)",
63
+ fontSize: "var(--text-base)",
64
+ outline: "none",
65
+ }}
66
+ />
67
+ </div>
68
+
69
+ <div style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
70
+ <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
71
+ <label htmlFor="password" style={{ fontSize: "var(--text-sm)", fontWeight: "var(--weight-medium)", color: "var(--color-text)" }}>
72
+ Password
73
+ </label>
74
+ <a href="/auth/forgot-password" style={{ fontSize: "var(--text-sm)", color: "var(--color-primary)" }}>
75
+ Forgot password?
76
+ </a>
77
+ </div>
78
+ <input
79
+ id="password"
80
+ type="password"
81
+ required
82
+ autoComplete="current-password"
83
+ value={password}
84
+ onChange={(e) => setPassword(e.target.value)}
85
+ style={{
86
+ padding: "var(--space-3) var(--space-4)",
87
+ borderRadius: "var(--radius-md)",
88
+ border: "1px solid var(--color-border)",
89
+ background: "var(--color-bg)",
90
+ color: "var(--color-text)",
91
+ fontSize: "var(--text-base)",
92
+ outline: "none",
93
+ }}
94
+ />
95
+ </div>
96
+
97
+ <button
98
+ type="submit"
99
+ disabled={loading}
100
+ style={{
101
+ padding: "var(--space-3) var(--space-4)",
102
+ borderRadius: "var(--radius-md)",
103
+ background: loading ? "var(--color-bg-muted)" : "var(--color-primary)",
104
+ color: "var(--color-primary-text)",
105
+ fontSize: "var(--text-base)",
106
+ fontWeight: "var(--weight-semibold)",
107
+ border: "none",
108
+ cursor: loading ? "not-allowed" : "pointer",
109
+ transition: "background var(--transition-fast)",
110
+ }}
111
+ >
112
+ {loading ? "Signing in…" : "Sign in"}
113
+ </button>
114
+
115
+ <p style={{ textAlign: "center", fontSize: "var(--text-sm)", color: "var(--color-text-muted)" }}>
116
+ No account?{" "}
117
+ <a href="/auth/register" style={{ color: "var(--color-primary)", fontWeight: "var(--weight-medium)" }}>
118
+ Create one
119
+ </a>
120
+ </p>
121
+ </form>
122
+ );
123
+ }
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useAuth } from "./AuthProvider";
6
+
7
+ export function RegisterForm() {
8
+ const { register } = useAuth();
9
+ const router = useRouter();
10
+ const [name, setName] = useState("");
11
+ const [email, setEmail] = useState("");
12
+ const [password, setPassword] = useState("");
13
+ const [error, setError] = useState("");
14
+ const [loading, setLoading] = useState(false);
15
+
16
+ async function handleSubmit(e: React.FormEvent) {
17
+ e.preventDefault();
18
+ setError("");
19
+ setLoading(true);
20
+ try {
21
+ await register(name, email, password);
22
+ router.push("/");
23
+ } catch (err) {
24
+ setError(err instanceof Error ? err.message : "Registration failed");
25
+ } finally {
26
+ setLoading(false);
27
+ }
28
+ }
29
+
30
+ const inputStyle: React.CSSProperties = {
31
+ padding: "var(--space-3) var(--space-4)",
32
+ borderRadius: "var(--radius-md)",
33
+ border: "1px solid var(--color-border)",
34
+ background: "var(--color-bg)",
35
+ color: "var(--color-text)",
36
+ fontSize: "var(--text-base)",
37
+ outline: "none",
38
+ };
39
+
40
+ return (
41
+ <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "var(--space-4)" }}>
42
+ {error && (
43
+ <div
44
+ role="alert"
45
+ style={{
46
+ padding: "var(--space-3) var(--space-4)",
47
+ borderRadius: "var(--radius-md)",
48
+ background: "var(--color-error-subtle)",
49
+ color: "var(--color-error-text)",
50
+ fontSize: "var(--text-sm)",
51
+ }}
52
+ >
53
+ {error}
54
+ </div>
55
+ )}
56
+
57
+ {(["Name", "Email", "Password"] as const).map((field) => (
58
+ <div key={field} style={{ display: "flex", flexDirection: "column", gap: "var(--space-2)" }}>
59
+ <label
60
+ htmlFor={field.toLowerCase()}
61
+ style={{ fontSize: "var(--text-sm)", fontWeight: "var(--weight-medium)", color: "var(--color-text)" }}
62
+ >
63
+ {field}
64
+ </label>
65
+ <input
66
+ id={field.toLowerCase()}
67
+ type={field === "Password" ? "password" : field === "Email" ? "email" : "text"}
68
+ required
69
+ value={field === "Name" ? name : field === "Email" ? email : password}
70
+ onChange={(e) => {
71
+ if (field === "Name") setName(e.target.value);
72
+ else if (field === "Email") setEmail(e.target.value);
73
+ else setPassword(e.target.value);
74
+ }}
75
+ style={inputStyle}
76
+ />
77
+ </div>
78
+ ))}
79
+
80
+ <button
81
+ type="submit"
82
+ disabled={loading}
83
+ style={{
84
+ padding: "var(--space-3) var(--space-4)",
85
+ borderRadius: "var(--radius-md)",
86
+ background: loading ? "var(--color-bg-muted)" : "var(--color-primary)",
87
+ color: "var(--color-primary-text)",
88
+ fontSize: "var(--text-base)",
89
+ fontWeight: "var(--weight-semibold)",
90
+ border: "none",
91
+ cursor: loading ? "not-allowed" : "pointer",
92
+ transition: "background var(--transition-fast)",
93
+ }}
94
+ >
95
+ {loading ? "Creating account…" : "Create account"}
96
+ </button>
97
+
98
+ <p style={{ textAlign: "center", fontSize: "var(--text-sm)", color: "var(--color-text-muted)" }}>
99
+ Already have an account?{" "}
100
+ <a href="/auth/login" style={{ color: "var(--color-primary)", fontWeight: "var(--weight-medium)" }}>
101
+ Sign in
102
+ </a>
103
+ </p>
104
+ </form>
105
+ );
106
+ }
@@ -0,0 +1,43 @@
1
+ import { SignJWT, jwtVerify } from "jose";
2
+
3
+ const ACCESS_SECRET = new TextEncoder().encode(
4
+ process.env.JWT_ACCESS_SECRET ?? "change-me-in-production-min-32-chars!!"
5
+ );
6
+ const REFRESH_SECRET = new TextEncoder().encode(
7
+ process.env.JWT_REFRESH_SECRET ?? "change-me-refresh-in-production-32chars"
8
+ );
9
+
10
+ export interface TokenPayload {
11
+ sub: string;
12
+ email: string;
13
+ iat?: number;
14
+ exp?: number;
15
+ }
16
+
17
+ export async function signToken(
18
+ payload: { sub: string; email: string },
19
+ type: "access" | "refresh" = "access"
20
+ ): Promise<string> {
21
+ const secret = type === "access" ? ACCESS_SECRET : REFRESH_SECRET;
22
+ const expiresIn = type === "access" ? "15m" : "30d";
23
+ return new SignJWT({ email: payload.email })
24
+ .setProtectedHeader({ alg: "HS256" })
25
+ .setSubject(payload.sub)
26
+ .setIssuedAt()
27
+ .setExpirationTime(expiresIn)
28
+ .sign(secret);
29
+ }
30
+
31
+ export async function verifyToken(
32
+ token: string,
33
+ type: "access" | "refresh" = "access"
34
+ ): Promise<TokenPayload> {
35
+ const secret = type === "access" ? ACCESS_SECRET : REFRESH_SECRET;
36
+ const { payload } = await jwtVerify(token, secret);
37
+ return {
38
+ sub: payload.sub as string,
39
+ email: payload["email"] as string,
40
+ iat: payload.iat,
41
+ exp: payload.exp,
42
+ };
43
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "template": "nextjs-compact",
4
+ "files": [
5
+ { "source": "files/app/auth/login/page.tsx", "destination": "app/auth/login/page.tsx" },
6
+ { "source": "files/app/auth/register/page.tsx", "destination": "app/auth/register/page.tsx" },
7
+ { "source": "files/app/auth/forgot-password/page.tsx", "destination": "app/auth/forgot-password/page.tsx" },
8
+ { "source": "files/app/auth/reset-password/page.tsx", "destination": "app/auth/reset-password/page.tsx" },
9
+ { "source": "files/app/api/auth/[...route]/route.ts", "destination": "app/api/auth/[...route]/route.ts" },
10
+ { "source": "files/components/auth/LoginForm.tsx", "destination": "components/auth/LoginForm.tsx" },
11
+ { "source": "files/components/auth/RegisterForm.tsx", "destination": "components/auth/RegisterForm.tsx" },
12
+ { "source": "files/components/auth/AuthProvider.tsx", "destination": "components/auth/AuthProvider.tsx" },
13
+ { "source": "files/lib/auth/authService.ts", "destination": "lib/auth/authService.ts" }
14
+ ],
15
+ "injections": [
16
+ {
17
+ "file": "app/layout.tsx",
18
+ "marker": "{/* devkit:auth:provider */}",
19
+ "wrap": { "open": "<AuthProvider>", "close": "</AuthProvider>" },
20
+ "importLine": "import { AuthProvider } from '@/components/auth/AuthProvider';"
21
+ }
22
+ ],
23
+ "dependencies": {
24
+ "frontend": ["jsonwebtoken", "js-cookie"],
25
+ "devFrontend": ["@types/js-cookie"]
26
+ },
27
+ "envVars": [
28
+ "JWT_ACCESS_SECRET",
29
+ "JWT_REFRESH_SECRET",
30
+ "GOOGLE_CLIENT_ID",
31
+ "GOOGLE_CLIENT_SECRET",
32
+ "SMTP_HOST",
33
+ "SMTP_PORT",
34
+ "SMTP_USER",
35
+ "SMTP_PASSWORD"
36
+ ]
37
+ }
@@ -0,0 +1,14 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ import { verifyToken } from "../services/jwtService.js";
3
+
4
+ export function requireAuth(req: Request, res: Response, next: NextFunction) {
5
+ const token = req.headers.authorization?.replace("Bearer ", "");
6
+ if (!token) { res.status(401).json({ message: "Unauthorized" }); return; }
7
+ try {
8
+ const payload = verifyToken(token);
9
+ (req as Request & { user: { id: string; email: string } }).user = { id: payload.sub, email: payload.email };
10
+ next();
11
+ } catch {
12
+ res.status(401).json({ message: "Invalid or expired token" });
13
+ }
14
+ }
@@ -0,0 +1,83 @@
1
+ import { Router, Request, Response } from "express";
2
+ import { signToken, verifyToken } from "../services/jwtService.js";
3
+
4
+ const router = Router();
5
+
6
+ router.post("/login", async (req: Request, res: Response) => {
7
+ const { email, password } = req.body;
8
+ if (!email || !password) { res.status(400).json({ message: "Email and password required" }); return; }
9
+
10
+ // TODO: replace with real DB lookup + bcrypt.compare
11
+ const user = await findUserByCredentials(email, password);
12
+ if (!user) { res.status(401).json({ message: "Invalid email or password" }); return; }
13
+
14
+ const token = await signToken({ sub: user.id, email: user.email });
15
+ res.json({ user, token });
16
+ });
17
+
18
+ router.post("/register", async (req: Request, res: Response) => {
19
+ const { name, email, password } = req.body;
20
+ if (!name || !email || !password) { res.status(400).json({ message: "Name, email, and password required" }); return; }
21
+ if (password.length < 8) { res.status(400).json({ message: "Password must be at least 8 characters" }); return; }
22
+
23
+ // TODO: replace with real user creation
24
+ const user = await createUser(name, email, password);
25
+ const token = await signToken({ sub: user.id, email: user.email });
26
+ res.status(201).json({ user, token });
27
+ });
28
+
29
+ router.get("/me", async (req: Request, res: Response) => {
30
+ const authHeader = req.headers.authorization;
31
+ const token = authHeader?.replace("Bearer ", "");
32
+ if (!token) { res.status(401).json({ message: "Unauthorized" }); return; }
33
+ try {
34
+ const payload = await verifyToken(token);
35
+ const user = await findUserById(payload.sub);
36
+ if (!user) { res.status(404).json({ message: "User not found" }); return; }
37
+ res.json({ user });
38
+ } catch {
39
+ res.status(401).json({ message: "Invalid token" });
40
+ }
41
+ });
42
+
43
+ router.post("/forgot-password", async (req: Request, res: Response) => {
44
+ const { email } = req.body;
45
+ if (!email) { res.status(400).json({ message: "Email required" }); return; }
46
+ // TODO: generate reset token, store in DB, send email
47
+ await sendPasswordResetEmail(email);
48
+ res.json({ ok: true });
49
+ });
50
+
51
+ router.post("/reset-password", async (req: Request, res: Response) => {
52
+ const { token, password } = req.body;
53
+ if (!token || !password) { res.status(400).json({ message: "Token and password required" }); return; }
54
+ // TODO: verify token, update password in DB
55
+ await resetUserPassword(token, password);
56
+ res.json({ ok: true });
57
+ });
58
+
59
+ router.post("/logout", (_req: Request, res: Response) => {
60
+ res.clearCookie("refresh_token");
61
+ res.json({ ok: true });
62
+ });
63
+
64
+ // ── Stubs (replace with real DB calls) ───────────────────────────────────────
65
+
66
+ async function findUserByCredentials(email: string, _password: string) {
67
+ void email;
68
+ return null as { id: string; name: string; email: string } | null;
69
+ }
70
+
71
+ async function createUser(name: string, email: string, _password: string) {
72
+ return { id: crypto.randomUUID(), name, email };
73
+ }
74
+
75
+ async function findUserById(id: string) {
76
+ void id;
77
+ return null as { id: string; name: string; email: string } | null;
78
+ }
79
+
80
+ async function sendPasswordResetEmail(email: string) { void email; }
81
+ async function resetUserPassword(token: string, password: string) { void token; void password; }
82
+
83
+ export default router;
@@ -0,0 +1,19 @@
1
+ import jwt from "jsonwebtoken";
2
+
3
+ const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET ?? "change-me-min-32-chars-access!!";
4
+ const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET ?? "change-me-min-32-chars-refresh!";
5
+
6
+ export interface TokenPayload { sub: string; email: string; iat?: number; exp?: number }
7
+
8
+ export function signToken(payload: { sub: string; email: string }, type: "access" | "refresh" = "access"): string {
9
+ return jwt.sign(
10
+ { email: payload.email },
11
+ type === "access" ? ACCESS_SECRET : REFRESH_SECRET,
12
+ { subject: payload.sub, expiresIn: type === "access" ? "15m" : "30d" }
13
+ );
14
+ }
15
+
16
+ export function verifyToken(token: string, type: "access" | "refresh" = "access"): TokenPayload {
17
+ const decoded = jwt.verify(token, type === "access" ? ACCESS_SECRET : REFRESH_SECRET) as jwt.JwtPayload;
18
+ return { sub: decoded.sub as string, email: decoded["email"] as string, iat: decoded.iat, exp: decoded.exp };
19
+ }
@@ -0,0 +1,72 @@
1
+ import { createContext, useContext, useEffect, useState } from "react";
2
+
3
+ interface User { id: string; email: string; name: string; avatar?: string }
4
+
5
+ interface AuthContextValue {
6
+ user: User | null;
7
+ loading: boolean;
8
+ login: (email: string, password: string) => Promise<void>;
9
+ register: (name: string, email: string, password: string) => Promise<void>;
10
+ logout: () => Promise<void>;
11
+ }
12
+
13
+ const AuthContext = createContext<AuthContextValue | null>(null);
14
+
15
+ const API = import.meta.env.VITE_API_URL ?? "http://localhost:3000";
16
+
17
+ export function AuthProvider({ children }: { children: React.ReactNode }) {
18
+ const [user, setUser] = useState<User | null>(null);
19
+ const [loading, setLoading] = useState(true);
20
+
21
+ useEffect(() => {
22
+ const token = localStorage.getItem("access_token");
23
+ if (!token) { setLoading(false); return; }
24
+ fetch(`${API}/api/auth/me`, {
25
+ headers: { Authorization: `Bearer ${token}` },
26
+ })
27
+ .then((r) => (r.ok ? r.json() : null))
28
+ .then((d) => setUser(d?.user ?? null))
29
+ .finally(() => setLoading(false));
30
+ }, []);
31
+
32
+ async function login(email: string, password: string) {
33
+ const res = await fetch(`${API}/api/auth/login`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({ email, password }),
37
+ });
38
+ if (!res.ok) { const d = await res.json(); throw new Error(d.message || "Login failed"); }
39
+ const { user, token } = await res.json();
40
+ localStorage.setItem("access_token", token);
41
+ setUser(user);
42
+ }
43
+
44
+ async function register(name: string, email: string, password: string) {
45
+ const res = await fetch(`${API}/api/auth/register`, {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ body: JSON.stringify({ name, email, password }),
49
+ });
50
+ if (!res.ok) { const d = await res.json(); throw new Error(d.message || "Registration failed"); }
51
+ const { user, token } = await res.json();
52
+ localStorage.setItem("access_token", token);
53
+ setUser(user);
54
+ }
55
+
56
+ async function logout() {
57
+ localStorage.removeItem("access_token");
58
+ setUser(null);
59
+ }
60
+
61
+ return (
62
+ <AuthContext.Provider value={{ user, loading, login, register, logout }}>
63
+ {children}
64
+ </AuthContext.Provider>
65
+ );
66
+ }
67
+
68
+ export function useAuth() {
69
+ const ctx = useContext(AuthContext);
70
+ if (!ctx) throw new Error("useAuth must be used inside <AuthProvider>");
71
+ return ctx;
72
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "template": "vite-express-tauri",
4
+ "files": [
5
+ { "source": "files/src/pages/auth/LoginPage.tsx", "destination": "src/pages/auth/LoginPage.tsx" },
6
+ { "source": "files/src/pages/auth/RegisterPage.tsx", "destination": "src/pages/auth/RegisterPage.tsx" },
7
+ { "source": "files/src/pages/auth/ForgotPasswordPage.tsx", "destination": "src/pages/auth/ForgotPasswordPage.tsx" },
8
+ { "source": "files/src/pages/auth/ResetPasswordPage.tsx", "destination": "src/pages/auth/ResetPasswordPage.tsx" },
9
+ { "source": "files/src/components/auth/LoginForm.tsx", "destination": "src/components/auth/LoginForm.tsx" },
10
+ { "source": "files/src/components/auth/RegisterForm.tsx","destination": "src/components/auth/RegisterForm.tsx" },
11
+ { "source": "files/src/contexts/AuthContext.tsx", "destination": "src/contexts/AuthContext.tsx" },
12
+ { "source": "files/src/lib/authService.ts", "destination": "src/lib/authService.ts" },
13
+ { "source": "files/server/routes/auth.ts", "destination": "server/src/routes/auth.ts" },
14
+ { "source": "files/server/middleware/requireAuth.ts", "destination": "server/src/middleware/requireAuth.ts" },
15
+ { "source": "files/server/services/jwtService.ts", "destination": "server/src/services/jwtService.ts" }
16
+ ],
17
+ "injections": [
18
+ {
19
+ "file": "src/main.tsx",
20
+ "marker": "/* devkit:auth:routes */",
21
+ "importLine": "import { LoginPage } from './pages/auth/LoginPage';",
22
+ "routeLine": "<Route path=\"/auth/login\" element={<LoginPage />} />"
23
+ },
24
+ {
25
+ "file": "server/src/index.ts",
26
+ "marker": "/* devkit:auth:api */",
27
+ "importLine": "import authRouter from './routes/auth';",
28
+ "routeLine": "app.use('/api/auth', authRouter);"
29
+ }
30
+ ],
31
+ "dependencies": {
32
+ "frontend": ["js-cookie"],
33
+ "devFrontend": ["@types/js-cookie"],
34
+ "backend": ["jsonwebtoken", "bcryptjs", "nodemailer"],
35
+ "devBackend": ["@types/jsonwebtoken", "@types/bcryptjs", "@types/nodemailer"]
36
+ },
37
+ "envVars": [
38
+ "JWT_ACCESS_SECRET",
39
+ "JWT_REFRESH_SECRET",
40
+ "GOOGLE_CLIENT_ID",
41
+ "GOOGLE_CLIENT_SECRET",
42
+ "SMTP_HOST",
43
+ "SMTP_PORT",
44
+ "SMTP_USER",
45
+ "SMTP_PASSWORD"
46
+ ]
47
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "hero-section",
3
+ "displayName": "Hero Section",
4
+ "description": "Landing page hero banner with multiple design variants. Only the chosen variant is downloaded.",
5
+ "category": "marketing",
6
+ "icon": "layout",
7
+ "author": "Roboticela",
8
+ "license": "MIT",
9
+ "tags": ["hero", "landing", "marketing", "banner"],
10
+ "templates": ["nextjs-compact", "vite-express-tauri"],
11
+ "platforms": ["web"],
12
+ "hasVariants": true,
13
+ "variants": [
14
+ { "id": "centered", "label": "Centered", "description": "Classic centered heading + subheading + CTA buttons" },
15
+ { "id": "split-image", "label": "Split Image", "description": "Text on left, product screenshot on right" },
16
+ { "id": "gradient-mesh", "label": "Gradient Mesh", "description": "Animated CSS gradient mesh background" },
17
+ { "id": "minimal", "label": "Minimal", "description": "Typography-only, no background decorations" }
18
+ ],
19
+ "versions": {
20
+ "latest": "1.0.0",
21
+ "stable": "1.0.0",
22
+ "history": [
23
+ { "version": "1.0.0", "releaseDate": "2026-04-05", "breaking": false }
24
+ ]
25
+ },
26
+ "requiredConfig": ["site.name", "site.description"],
27
+ "optionalConfig": []
28
+ }
@@ -0,0 +1,97 @@
1
+ export function HeroSection({
2
+ headline = "Build faster. Ship smarter.",
3
+ subheadline = "The DevKit that turns your ideas into production-ready apps in minutes.",
4
+ primaryCta = { label: "Get started", href: "/auth/register" },
5
+ secondaryCta = { label: "View docs", href: "/docs" },
6
+ }: {
7
+ headline?: string;
8
+ subheadline?: string;
9
+ primaryCta?: { label: string; href: string };
10
+ secondaryCta?: { label: string; href: string };
11
+ }) {
12
+ return (
13
+ <section
14
+ style={{
15
+ display: "flex",
16
+ flexDirection: "column",
17
+ alignItems: "center",
18
+ textAlign: "center",
19
+ padding: "var(--space-24) var(--space-4)",
20
+ gap: "var(--space-6)",
21
+ background: "var(--color-bg)",
22
+ }}
23
+ >
24
+ <span
25
+ style={{
26
+ display: "inline-block",
27
+ padding: "var(--space-1) var(--space-3)",
28
+ borderRadius: "var(--radius-full)",
29
+ background: "var(--color-primary-subtle)",
30
+ color: "var(--color-primary)",
31
+ fontSize: "var(--text-sm)",
32
+ fontWeight: "var(--weight-medium)",
33
+ }}
34
+ >
35
+ Now in public beta
36
+ </span>
37
+
38
+ <h1
39
+ style={{
40
+ fontSize: "clamp(var(--text-4xl), 6vw, var(--text-6xl))",
41
+ fontWeight: "var(--weight-extrabold)",
42
+ fontFamily: "var(--font-display)",
43
+ color: "var(--color-text)",
44
+ lineHeight: "var(--leading-tight)",
45
+ maxWidth: "720px",
46
+ }}
47
+ >
48
+ {headline}
49
+ </h1>
50
+
51
+ <p
52
+ style={{
53
+ fontSize: "var(--text-xl)",
54
+ color: "var(--color-text-muted)",
55
+ maxWidth: "560px",
56
+ lineHeight: "var(--leading-relaxed)",
57
+ }}
58
+ >
59
+ {subheadline}
60
+ </p>
61
+
62
+ <div style={{ display: "flex", gap: "var(--space-4)", flexWrap: "wrap", justifyContent: "center" }}>
63
+ <a
64
+ href={primaryCta.href}
65
+ style={{
66
+ padding: "var(--space-3) var(--space-8)",
67
+ borderRadius: "var(--radius-md)",
68
+ background: "var(--color-primary)",
69
+ color: "var(--color-primary-text)",
70
+ fontSize: "var(--text-base)",
71
+ fontWeight: "var(--weight-semibold)",
72
+ textDecoration: "none",
73
+ transition: "background var(--transition-fast)",
74
+ }}
75
+ >
76
+ {primaryCta.label}
77
+ </a>
78
+ <a
79
+ href={secondaryCta.href}
80
+ style={{
81
+ padding: "var(--space-3) var(--space-8)",
82
+ borderRadius: "var(--radius-md)",
83
+ background: "transparent",
84
+ color: "var(--color-text)",
85
+ fontSize: "var(--text-base)",
86
+ fontWeight: "var(--weight-semibold)",
87
+ textDecoration: "none",
88
+ border: "1px solid var(--color-border)",
89
+ transition: "border-color var(--transition-fast)",
90
+ }}
91
+ >
92
+ {secondaryCta.label}
93
+ </a>
94
+ </div>
95
+ </section>
96
+ );
97
+ }