@surajprasad/create-starterkit 0.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 (84) hide show
  1. package/README.md +30 -0
  2. package/index.js +327 -0
  3. package/package.json +29 -0
  4. package/templates/mern/backend/.env.example +11 -0
  5. package/templates/mern/backend/Dockerfile +13 -0
  6. package/templates/mern/backend/package.json +23 -0
  7. package/templates/mern/backend/server.js +25 -0
  8. package/templates/mern/backend/src/app.js +48 -0
  9. package/templates/mern/backend/src/config/db.js +17 -0
  10. package/templates/mern/backend/src/config/env.js +38 -0
  11. package/templates/mern/backend/src/middleware/authMiddleware.js +30 -0
  12. package/templates/mern/backend/src/middleware/errorMiddleware.js +17 -0
  13. package/templates/mern/backend/src/middleware/notFound.middleware.js +9 -0
  14. package/templates/mern/backend/src/modules/auth/auth.controller.js +45 -0
  15. package/templates/mern/backend/src/modules/auth/auth.model.js +20 -0
  16. package/templates/mern/backend/src/modules/auth/auth.routes.js +55 -0
  17. package/templates/mern/backend/src/modules/auth/auth.service.js +185 -0
  18. package/templates/mern/backend/src/modules/auth/dto/login.dto.js +12 -0
  19. package/templates/mern/backend/src/modules/auth/dto/register.dto.js +14 -0
  20. package/templates/mern/backend/src/modules/email/dto/sendEmail.dto.js +16 -0
  21. package/templates/mern/backend/src/modules/email/email.controller.js +33 -0
  22. package/templates/mern/backend/src/modules/email/email.routes.js +13 -0
  23. package/templates/mern/backend/src/modules/email/email.service.js +88 -0
  24. package/templates/mern/backend/src/modules/email/templates/resetPassword.html +47 -0
  25. package/templates/mern/backend/src/modules/email/templates/verifyEmail.html +45 -0
  26. package/templates/mern/backend/src/modules/email/templates/welcome.html +47 -0
  27. package/templates/mern/backend/src/utils/apiResponse.util.js +9 -0
  28. package/templates/mern/backend/src/utils/asyncHandler.util.js +6 -0
  29. package/templates/mern/backend/src/utils/generateOTP.util.js +10 -0
  30. package/templates/mern/backend/src/utils/generateResetToken.util.js +8 -0
  31. package/templates/mern/backend/src/utils/generateToken.util.js +17 -0
  32. package/templates/mern/backend/src/utils/hashPassword.util.js +11 -0
  33. package/templates/mern/backend/src/utils/validateDto.util.js +18 -0
  34. package/templates/mern/frontend/.env.example +1 -0
  35. package/templates/mern/frontend/Dockerfile +13 -0
  36. package/templates/mern/frontend/index.html +13 -0
  37. package/templates/mern/frontend/package.json +23 -0
  38. package/templates/mern/frontend/src/App.jsx +102 -0
  39. package/templates/mern/frontend/src/main.jsx +14 -0
  40. package/templates/mern/frontend/src/modules/auth/components/ProtectedRoute.jsx +10 -0
  41. package/templates/mern/frontend/src/modules/auth/index.js +6 -0
  42. package/templates/mern/frontend/src/modules/auth/pages/ForgotPasswordPage.jsx +64 -0
  43. package/templates/mern/frontend/src/modules/auth/pages/LoginPage.jsx +82 -0
  44. package/templates/mern/frontend/src/modules/auth/pages/RegisterPage.jsx +81 -0
  45. package/templates/mern/frontend/src/modules/auth/pages/ResetPasswordPage.jsx +78 -0
  46. package/templates/mern/frontend/src/modules/auth/pages/VerifyEmailPage.jsx +69 -0
  47. package/templates/mern/frontend/src/modules/auth/services/auth.service.js +34 -0
  48. package/templates/mern/frontend/src/modules/auth/store/authStore.js +37 -0
  49. package/templates/mern/frontend/src/modules/dashboard/index.js +2 -0
  50. package/templates/mern/frontend/src/modules/dashboard/pages/DashboardPage.jsx +41 -0
  51. package/templates/mern/frontend/src/shared/components/Button.jsx +31 -0
  52. package/templates/mern/frontend/src/shared/components/Input.jsx +23 -0
  53. package/templates/mern/frontend/src/shared/components/Toast.jsx +52 -0
  54. package/templates/mern/frontend/src/shared/services/api.js +20 -0
  55. package/templates/mern/frontend/src/shared/utils/formatError.util.js +8 -0
  56. package/templates/mern/frontend/src/shared/utils/storage.util.js +25 -0
  57. package/templates/mern/frontend/vite.config.js +13 -0
  58. package/templates/mern/frontend-next/.env.example +1 -0
  59. package/templates/mern/frontend-next/app/forgot-password/page.js +8 -0
  60. package/templates/mern/frontend-next/app/layout.js +15 -0
  61. package/templates/mern/frontend-next/app/login/page.js +8 -0
  62. package/templates/mern/frontend-next/app/page.js +22 -0
  63. package/templates/mern/frontend-next/app/register/page.js +8 -0
  64. package/templates/mern/frontend-next/app/reset-password/page.js +8 -0
  65. package/templates/mern/frontend-next/app/verify-email/page.js +8 -0
  66. package/templates/mern/frontend-next/jsconfig.json +6 -0
  67. package/templates/mern/frontend-next/next.config.mjs +7 -0
  68. package/templates/mern/frontend-next/package.json +18 -0
  69. package/templates/mern/frontend-next/src/modules/auth/components/ProtectedRoute.jsx +19 -0
  70. package/templates/mern/frontend-next/src/modules/auth/pages/ForgotPasswordPage.jsx +66 -0
  71. package/templates/mern/frontend-next/src/modules/auth/pages/LoginPage.jsx +88 -0
  72. package/templates/mern/frontend-next/src/modules/auth/pages/RegisterPage.jsx +84 -0
  73. package/templates/mern/frontend-next/src/modules/auth/pages/ResetPasswordPage.jsx +76 -0
  74. package/templates/mern/frontend-next/src/modules/auth/pages/VerifyEmailPage.jsx +71 -0
  75. package/templates/mern/frontend-next/src/modules/auth/services/auth.service.js +29 -0
  76. package/templates/mern/frontend-next/src/modules/auth/store/authStore.js +37 -0
  77. package/templates/mern/frontend-next/src/modules/dashboard/pages/DashboardPage.jsx +46 -0
  78. package/templates/mern/frontend-next/src/shared/components/Button.jsx +31 -0
  79. package/templates/mern/frontend-next/src/shared/components/Input.jsx +24 -0
  80. package/templates/mern/frontend-next/src/shared/components/Toast.jsx +52 -0
  81. package/templates/mern/frontend-next/src/shared/services/api.js +25 -0
  82. package/templates/mern/frontend-next/src/shared/utils/formatError.util.js +8 -0
  83. package/templates/mern/frontend-next/src/shared/utils/storage.util.js +28 -0
  84. package/templates/mern/package.json +6 -0
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ import { useMemo, useState } from "react";
4
+ import Link from "next/link";
5
+ import { useSearchParams } from "next/navigation";
6
+
7
+ import { Button } from "../../../shared/components/Button.jsx";
8
+ import { Input } from "../../../shared/components/Input.jsx";
9
+ import { Toast } from "../../../shared/components/Toast.jsx";
10
+ import { formatError } from "../../../shared/utils/formatError.util.js";
11
+ import { resetPasswordApi } from "../services/auth.service.js";
12
+
13
+ export const ResetPasswordPage = () => {
14
+ const params = useSearchParams();
15
+ const token = useMemo(() => params.get("token") || "", [params]);
16
+
17
+ const [password, setPassword] = useState("");
18
+ const [loading, setLoading] = useState(false);
19
+ const [toast, setToast] = useState(null);
20
+
21
+ const onSubmit = async (e) => {
22
+ e.preventDefault();
23
+ setLoading(true);
24
+ try {
25
+ await resetPasswordApi({ token, password });
26
+ setToast({ type: "success", message: "Password reset successfully. You can login now." });
27
+ } catch (err) {
28
+ setToast({ type: "error", message: formatError(err) });
29
+ } finally {
30
+ setLoading(false);
31
+ }
32
+ };
33
+
34
+ return (
35
+ <div style={{ minHeight: "100vh", display: "grid", placeItems: "center", padding: 24 }}>
36
+ <form
37
+ onSubmit={onSubmit}
38
+ style={{
39
+ width: "100%",
40
+ maxWidth: 420,
41
+ background: "white",
42
+ border: "1px solid #E5E7EB",
43
+ borderRadius: 16,
44
+ padding: 20
45
+ }}
46
+ >
47
+ <h1 style={{ margin: 0, fontSize: 22 }}>Reset password</h1>
48
+ <p style={{ marginTop: 6, marginBottom: 16, color: "#6B7280", fontSize: 13 }}>
49
+ Set a new password for your account.
50
+ </p>
51
+
52
+ <div style={{ display: "grid", gap: 12 }}>
53
+ <Input label="Reset token" value={token} readOnly style={{ background: "#F9FAFB" }} />
54
+ <Input
55
+ label="New password"
56
+ type="password"
57
+ value={password}
58
+ onChange={(e) => setPassword(e.target.value)}
59
+ />
60
+ <Button disabled={loading || !token} type="submit">
61
+ {loading ? "Please wait..." : "Reset password"}
62
+ </Button>
63
+ </div>
64
+
65
+ <div style={{ marginTop: 14 }}>
66
+ <Link href="/login" style={{ color: "#4F46E5", fontWeight: 700, fontSize: 13 }}>
67
+ Back to login
68
+ </Link>
69
+ </div>
70
+ </form>
71
+
72
+ <Toast message={toast?.message} type={toast?.type} onClose={() => setToast(null)} />
73
+ </div>
74
+ );
75
+ };
76
+
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+
6
+ import { Button } from "../../../shared/components/Button.jsx";
7
+ import { Input } from "../../../shared/components/Input.jsx";
8
+ import { Toast } from "../../../shared/components/Toast.jsx";
9
+ import { formatError } from "../../../shared/utils/formatError.util.js";
10
+ import { verifyEmailApi } from "../services/auth.service.js";
11
+ import { useAuthStore } from "../store/authStore.js";
12
+
13
+ export const VerifyEmailPage = () => {
14
+ const router = useRouter();
15
+ const refreshMe = useAuthStore((s) => s.refreshMe);
16
+
17
+ const [otp, setOtp] = useState("");
18
+ const [loading, setLoading] = useState(false);
19
+ const [toast, setToast] = useState(null);
20
+
21
+ const onSubmit = async (e) => {
22
+ e.preventDefault();
23
+ setLoading(true);
24
+ try {
25
+ await verifyEmailApi({ otp });
26
+ await refreshMe();
27
+ setToast({ type: "success", message: "Email verified" });
28
+ router.replace("/");
29
+ } catch (err) {
30
+ setToast({ type: "error", message: formatError(err) });
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+
36
+ return (
37
+ <div style={{ minHeight: "100vh", display: "grid", placeItems: "center", padding: 24 }}>
38
+ <form
39
+ onSubmit={onSubmit}
40
+ style={{
41
+ width: "100%",
42
+ maxWidth: 420,
43
+ background: "white",
44
+ border: "1px solid #E5E7EB",
45
+ borderRadius: 16,
46
+ padding: 20
47
+ }}
48
+ >
49
+ <h1 style={{ margin: 0, fontSize: 22 }}>Verify Email</h1>
50
+ <p style={{ marginTop: 6, marginBottom: 16, color: "#6B7280", fontSize: 13 }}>
51
+ Enter the 6-digit OTP sent to your email.
52
+ </p>
53
+
54
+ <div style={{ display: "grid", gap: 12 }}>
55
+ <Input
56
+ label="OTP"
57
+ value={otp}
58
+ onChange={(e) => setOtp(e.target.value)}
59
+ placeholder="123456"
60
+ />
61
+ <Button disabled={loading} type="submit">
62
+ {loading ? "Please wait..." : "Verify"}
63
+ </Button>
64
+ </div>
65
+ </form>
66
+
67
+ <Toast message={toast?.message} type={toast?.type} onClose={() => setToast(null)} />
68
+ </div>
69
+ );
70
+ };
71
+
@@ -0,0 +1,29 @@
1
+ import { apiFetch } from "../../../shared/services/api.js";
2
+
3
+ export const registerApi = async ({ name, email, password }) => {
4
+ return apiFetch("/api/auth/register", { method: "POST", body: { name, email, password } });
5
+ };
6
+
7
+ export const loginApi = async ({ email, password }) => {
8
+ return apiFetch("/api/auth/login", { method: "POST", body: { email, password } });
9
+ };
10
+
11
+ export const meApi = async () => {
12
+ return apiFetch("/api/auth/me");
13
+ };
14
+
15
+ export const verifyEmailApi = async ({ otp }) => {
16
+ return apiFetch("/api/auth/verify-email", { method: "POST", body: { otp } });
17
+ };
18
+
19
+ export const forgotPasswordApi = async ({ email }) => {
20
+ return apiFetch("/api/auth/forgot-password", { method: "POST", body: { email } });
21
+ };
22
+
23
+ export const resetPasswordApi = async ({ token, password }) => {
24
+ return apiFetch(`/api/auth/reset-password?token=${encodeURIComponent(token)}`, {
25
+ method: "POST",
26
+ body: { password }
27
+ });
28
+ };
29
+
@@ -0,0 +1,37 @@
1
+ import { create } from "zustand";
2
+
3
+ import { storage } from "../../../shared/utils/storage.util.js";
4
+ import { meApi } from "../services/auth.service.js";
5
+
6
+ export const useAuthStore = create((set, get) => ({
7
+ user: storage.get("user"),
8
+ token: storage.get("token"),
9
+ loading: false,
10
+
11
+ setAuth({ user, token }) {
12
+ storage.set("user", user);
13
+ storage.set("token", token);
14
+ set({ user, token });
15
+ },
16
+
17
+ clearAuth() {
18
+ storage.remove("user");
19
+ storage.remove("token");
20
+ set({ user: null, token: null });
21
+ },
22
+
23
+ async refreshMe() {
24
+ const token = get().token;
25
+ if (!token) return;
26
+ set({ loading: true });
27
+ try {
28
+ const res = await meApi();
29
+ const user = res?.data?.user || null;
30
+ storage.set("user", user);
31
+ set({ user });
32
+ } finally {
33
+ set({ loading: false });
34
+ }
35
+ }
36
+ }));
37
+
@@ -0,0 +1,46 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+
6
+ import { Button } from "../../../shared/components/Button.jsx";
7
+ import { Toast } from "../../../shared/components/Toast.jsx";
8
+ import { useAuthStore } from "../../auth/store/authStore.js";
9
+
10
+ export const DashboardPage = () => {
11
+ const router = useRouter();
12
+ const user = useAuthStore((s) => s.user);
13
+ const clearAuth = useAuthStore((s) => s.clearAuth);
14
+ const [toast, setToast] = useState(null);
15
+
16
+ const onLogout = () => {
17
+ clearAuth();
18
+ setToast({ type: "success", message: "Logged out" });
19
+ router.replace("/login");
20
+ };
21
+
22
+ return (
23
+ <div style={{ padding: 24, maxWidth: 860, margin: "0 auto" }}>
24
+ <div
25
+ style={{
26
+ background: "white",
27
+ border: "1px solid #E5E7EB",
28
+ borderRadius: 16,
29
+ padding: 18
30
+ }}
31
+ >
32
+ <h1 style={{ margin: 0, fontSize: 22 }}>Dashboard</h1>
33
+ <p style={{ marginTop: 8, color: "#6B7280", fontSize: 13 }}>
34
+ You are logged in as <strong>{user?.email || "unknown"}</strong>
35
+ </p>
36
+ <div style={{ marginTop: 14 }}>
37
+ <Button onClick={onLogout} variant="secondary">
38
+ Logout
39
+ </Button>
40
+ </div>
41
+ </div>
42
+ <Toast message={toast?.message} type={toast?.type} onClose={() => setToast(null)} />
43
+ </div>
44
+ );
45
+ };
46
+
@@ -0,0 +1,31 @@
1
+ export const Button = ({ children, variant = "primary", ...props }) => {
2
+ const styles =
3
+ variant === "primary"
4
+ ? {
5
+ background: "#4F46E5",
6
+ color: "white",
7
+ border: "1px solid #4F46E5"
8
+ }
9
+ : {
10
+ background: "white",
11
+ color: "#111827",
12
+ border: "1px solid #E5E7EB"
13
+ };
14
+
15
+ return (
16
+ <button
17
+ {...props}
18
+ style={{
19
+ ...styles,
20
+ padding: "10px 14px",
21
+ borderRadius: 10,
22
+ fontWeight: 700,
23
+ cursor: "pointer",
24
+ opacity: props.disabled ? 0.6 : 1
25
+ }}
26
+ >
27
+ {children}
28
+ </button>
29
+ );
30
+ };
31
+
@@ -0,0 +1,24 @@
1
+ export const Input = ({ label, ...props }) => {
2
+ return (
3
+ <label style={{ display: "block" }}>
4
+ {label ? (
5
+ <div style={{ marginBottom: 6, fontSize: 12, color: "#374151", fontWeight: 700 }}>
6
+ {label}
7
+ </div>
8
+ ) : null}
9
+ <input
10
+ {...props}
11
+ style={{
12
+ width: "100%",
13
+ padding: "10px 12px",
14
+ borderRadius: 10,
15
+ border: "1px solid #E5E7EB",
16
+ outline: "none",
17
+ fontSize: 14,
18
+ ...(props.style || {})
19
+ }}
20
+ />
21
+ </label>
22
+ );
23
+ };
24
+
@@ -0,0 +1,52 @@
1
+ import { useEffect } from "react";
2
+
3
+ export const Toast = ({ message, type = "info", onClose }) => {
4
+ useEffect(() => {
5
+ if (!message) return;
6
+ const t = setTimeout(() => onClose?.(), 3500);
7
+ return () => clearTimeout(t);
8
+ }, [message, onClose]);
9
+
10
+ if (!message) return null;
11
+
12
+ const bg =
13
+ type === "error" ? "#FEE2E2" : type === "success" ? "#DCFCE7" : "#E0E7FF";
14
+ const border =
15
+ type === "error" ? "#FCA5A5" : type === "success" ? "#86EFAC" : "#A5B4FC";
16
+
17
+ return (
18
+ <div
19
+ style={{
20
+ position: "fixed",
21
+ bottom: 16,
22
+ right: 16,
23
+ maxWidth: 360,
24
+ background: bg,
25
+ border: `1px solid ${border}`,
26
+ padding: "10px 12px",
27
+ borderRadius: 12,
28
+ color: "#111827",
29
+ boxShadow: "0 10px 20px rgba(0,0,0,0.08)"
30
+ }}
31
+ role="status"
32
+ >
33
+ <div style={{ display: "flex", gap: 10, alignItems: "start" }}>
34
+ <div style={{ flex: 1, fontSize: 13, lineHeight: 1.4 }}>{message}</div>
35
+ <button
36
+ onClick={onClose}
37
+ style={{
38
+ background: "transparent",
39
+ border: "none",
40
+ cursor: "pointer",
41
+ fontWeight: 900,
42
+ color: "#111827"
43
+ }}
44
+ aria-label="Close"
45
+ >
46
+ ×
47
+ </button>
48
+ </div>
49
+ </div>
50
+ );
51
+ };
52
+
@@ -0,0 +1,25 @@
1
+ import { storage } from "../utils/storage.util.js";
2
+
3
+ const baseURL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000";
4
+
5
+ export const apiFetch = async (path, { method = "GET", body, headers } = {}) => {
6
+ const token = storage.get("token");
7
+ const res = await fetch(`${baseURL}${path}`, {
8
+ method,
9
+ headers: {
10
+ "Content-Type": "application/json",
11
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
12
+ ...(headers || {})
13
+ },
14
+ body: body ? JSON.stringify(body) : undefined
15
+ });
16
+
17
+ const data = await res.json().catch(() => ({}));
18
+ if (!res.ok) {
19
+ const err = new Error(data?.message || "Request failed");
20
+ err.response = { data };
21
+ throw err;
22
+ }
23
+ return data;
24
+ };
25
+
@@ -0,0 +1,8 @@
1
+ export const formatError = (err) => {
2
+ const msg =
3
+ err?.response?.data?.message ||
4
+ err?.message ||
5
+ "Something went wrong";
6
+ return String(msg);
7
+ };
8
+
@@ -0,0 +1,28 @@
1
+ export const storage = {
2
+ get(key) {
3
+ try {
4
+ if (typeof window === "undefined") return null;
5
+ const raw = window.localStorage.getItem(key);
6
+ return raw ? JSON.parse(raw) : null;
7
+ } catch {
8
+ return null;
9
+ }
10
+ },
11
+ set(key, value) {
12
+ try {
13
+ if (typeof window === "undefined") return;
14
+ window.localStorage.setItem(key, JSON.stringify(value));
15
+ } catch {
16
+ // ignore
17
+ }
18
+ },
19
+ remove(key) {
20
+ try {
21
+ if (typeof window === "undefined") return;
22
+ window.localStorage.removeItem(key);
23
+ } catch {
24
+ // ignore
25
+ }
26
+ }
27
+ };
28
+
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "starterkit-root",
3
+ "version": "0.1.0",
4
+ "private": true
5
+ }
6
+