easy-starter 1.2.1 → 2.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.
Files changed (36) hide show
  1. package/README.md +32 -80
  2. package/bin/index.js +219 -44
  3. package/package.json +20 -11
  4. package/template/.cursorrules +16 -9
  5. package/template/README.md +81 -24
  6. package/template/env +0 -1
  7. package/template/gitignore +1 -1
  8. package/template/index.html +15 -0
  9. package/template/package.json +8 -9
  10. package/template/public/images/icon-16.png +0 -0
  11. package/template/public/images/icon-180.png +0 -0
  12. package/template/public/images/icon-192.png +0 -0
  13. package/template/public/images/icon-32.png +0 -0
  14. package/template/public/images/icon-512.png +0 -0
  15. package/template/{src → public}/site.webmanifest +6 -12
  16. package/template/server/index.tsx +249 -53
  17. package/template/server/mail.tsx +120 -0
  18. package/template/src/App.tsx +33 -7
  19. package/template/src/Router.tsx +11 -2
  20. package/template/src/index.tsx +28 -1
  21. package/template/src/pages/Index.tsx +45 -6
  22. package/template/src/pages/ResetPassword.tsx +126 -0
  23. package/template/src/pages/admin/Admin.tsx +360 -85
  24. package/template/src/pages/admin/AnalyticsTab.tsx +71 -0
  25. package/template/src/pages/admin/ErrorsTab.tsx +46 -0
  26. package/template/src/pages/admin/UsersTab.tsx +61 -0
  27. package/template/src/services/api.ts +5 -7
  28. package/template/src/services/common.ts +23 -0
  29. package/template/src/styles.css +460 -0
  30. package/template/tsconfig.json +2 -1
  31. package/template/types.ts +44 -3
  32. package/template/vite.config.ts +24 -0
  33. package/template/.parcelrc +0 -6
  34. package/template/src/images/icon.png +0 -0
  35. package/template/src/index.html +0 -16
  36. package/template/src/styles.scss +0 -292
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import React, { useState } from "react";
2
2
  import { Link } from "easy-page-router/react";
3
3
 
4
4
  import { api, useSSRApi } from "../services/api";
@@ -6,8 +6,29 @@ import { useHead } from "../services/common";
6
6
 
7
7
  import { Item } from "../../types";
8
8
 
9
+ // --- AUTH START ---
10
+ import { useUser, UserDialog } from "easy-user-auth/react";
11
+ import { UserProfile } from "../../types";
12
+ // --- AUTH END ---
13
+
9
14
  export default function Index() {
10
- useHead({ title: "Welcome to easy-starter", description: "The ultimate foundation for your next project." });
15
+ // --- OGIMAGE START ---
16
+ const ogImage = typeof window !== "undefined"
17
+ ? `${window.location.origin}/og-image.png?path=${encodeURIComponent(window.location.pathname)}`
18
+ : undefined;
19
+ // --- OGIMAGE END ---
20
+
21
+ useHead({
22
+ title: "Welcome to easy-starter",
23
+ description: "The ultimate foundation for your next project."
24
+ // --- OGIMAGE START ---
25
+ , image: ogImage
26
+ // --- OGIMAGE END ---
27
+ });
28
+
29
+ // --- AUTH START ---
30
+ const { isLoggedIn, user, loginUser, showUserDialog } = useUser<UserProfile>();
31
+ // --- AUTH END ---
11
32
 
12
33
  // This will run on the server, fetch the data, embed it in the HTML,
13
34
  // and hydrate perfectly on the client. Fully type-safe!
@@ -47,17 +68,35 @@ export default function Index() {
47
68
 
48
69
  return (
49
70
  <main>
71
+ {/* --- AUTH START --- */}
72
+ <div style={{ display: "flex", justifyContent: "flex-end", padding: "10px 0", borderBottom: "1px solid var(--border)", marginBottom: 20 }}>
73
+ {isLoggedIn ? (
74
+ <div style={{ display: "flex", alignItems: "center", gap: 15 }}>
75
+ <span>Logged in as: <strong>{user.name} ({user.role})</strong></span>
76
+ <button onClick={() => loginUser(null)}>Log Out</button>
77
+ </div>
78
+ ) : (
79
+ <button onClick={showUserDialog}>Log In / Sign Up</button>
80
+ )}
81
+ </div>
82
+ {/* --- AUTH END --- */}
83
+
50
84
  <header>
51
85
  <figure></figure>
52
86
  <h1>Welcome to easy-starter!</h1>
53
87
  <p>The ultimate foundation for your next project.</p>
54
- <Link href="/admin">
55
- Go to Admin Dashboard
56
- </Link>
88
+
89
+ {/* --- ADMIN START --- */}
90
+ <div style={{ marginTop: 20 }}>
91
+ <Link href="/admin">
92
+ → Go to Admin Dashboard
93
+ </Link>
94
+ </div>
95
+ {/* --- ADMIN END --- */}
57
96
  </header>
58
97
 
59
98
  <section>
60
- <h2>Items (SSR Hook)</h2>
99
+ <h2>Items</h2>
61
100
  {loading && <p>Loading from server...</p>}
62
101
  {error && <p>Error loading items: {error.message}</p>}
63
102
  {items && (
@@ -0,0 +1,126 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { useRouter } from "easy-page-router/react";
3
+ import { useUser } from "easy-user-auth/react";
4
+ import { useHead } from "../services/common";
5
+ import { UserProfile } from "../../types";
6
+
7
+ export default function ResetPasswordPage() {
8
+ useHead({ title: "Reset Password", description: "Enter a new password for your account." });
9
+
10
+ const { api, loginUser, dict } = useUser<UserProfile>();
11
+ const { push } = useRouter();
12
+
13
+ const [token, setToken] = useState<string | null>(null);
14
+ const [password, setPassword] = useState("");
15
+ const [passwordAgain, setPasswordAgain] = useState("");
16
+ const [error, setError] = useState<string | null>(null);
17
+ const [success, setSuccess] = useState<boolean>(false);
18
+ const [loading, setLoading] = useState<boolean>(false);
19
+
20
+ useEffect(() => {
21
+ if (typeof window !== "undefined") {
22
+ const params = new URLSearchParams(window.location.search);
23
+ const tok = params.get("token");
24
+ if (tok) {
25
+ setToken(tok);
26
+ } else {
27
+ setError("Reset token is missing in the URL.");
28
+ }
29
+ }
30
+ }, []);
31
+
32
+ let validationError = "";
33
+ if (password.length > 0 && password.length < 6) {
34
+ validationError = dict.userWrongPasswordLength;
35
+ } else if (password && passwordAgain && password !== passwordAgain) {
36
+ validationError = dict.userWrongPasswordMatch;
37
+ }
38
+
39
+ const handleSubmit = async (e: React.FormEvent) => {
40
+ e.preventDefault();
41
+ if (validationError || !password || !passwordAgain || !token) return;
42
+
43
+ setLoading(true);
44
+ setError(null);
45
+
46
+ const [userResult, err] = await api.updateForgottenPassword({ token, password });
47
+
48
+ if (err) {
49
+ setError(dict.userForgottenPasswordErrorChange);
50
+ } else if (userResult) {
51
+ setSuccess(true);
52
+ setTimeout(() => {
53
+ loginUser(userResult);
54
+ push("/");
55
+ }, 2000);
56
+ }
57
+ setLoading(false);
58
+ };
59
+
60
+ return (
61
+ <main style={{ maxWidth: 450, margin: "60px auto" }}>
62
+ <h2 className="easy-user-auth-title" style={{ marginBottom: 20 }}>🔑 {dict.userForgottenPasswordChange}</h2>
63
+
64
+ {error && !token && (
65
+ <div className="easy-user-auth-alert" style={{ marginBottom: 20 }}>{error}</div>
66
+ )}
67
+
68
+ {success ? (
69
+ <div className="easy-user-auth-alert" style={{ background: "rgba(16, 185, 129, 0.1)", border: "1px solid rgba(16, 185, 129, 0.2)", color: "#34d399", marginBottom: 20 }}>
70
+ {dict.userForgottenPasswordSuccessChange}
71
+ </div>
72
+ ) : (
73
+ token && (
74
+ <form onSubmit={handleSubmit} className="easy-user-auth-form">
75
+ <div className="easy-user-auth-form-input">
76
+ <label className="easy-user-auth-label">{dict.password}</label>
77
+ <input
78
+ type="password"
79
+ className="easy-user-auth-input"
80
+ value={password}
81
+ onChange={(e) => setPassword(e.target.value)}
82
+ required
83
+ maxLength={50}
84
+ />
85
+ </div>
86
+
87
+ <div className="easy-user-auth-form-input">
88
+ <label className="easy-user-auth-label">{dict.passwordAgain}</label>
89
+ <input
90
+ type="password"
91
+ className="easy-user-auth-input"
92
+ value={passwordAgain}
93
+ onChange={(e) => setPasswordAgain(e.target.value)}
94
+ required
95
+ maxLength={50}
96
+ />
97
+ </div>
98
+
99
+ {error && <div className="easy-user-auth-alert">{error}</div>}
100
+ {validationError && <p className="easy-user-auth-info" style={{ color: "#ef4444" }}>{validationError}</p>}
101
+
102
+ <div style={{ display: "flex", gap: 15, marginTop: 10 }}>
103
+ <button
104
+ type="submit"
105
+ className="easy-user-auth-submit-btn"
106
+ disabled={!!validationError || !password || !passwordAgain || loading}
107
+ style={{ flex: 1 }}
108
+ >
109
+ {loading ? "Updating..." : dict.change}
110
+ </button>
111
+ <button
112
+ type="button"
113
+ className="easy-user-auth-back-btn"
114
+ onClick={() => push("/")}
115
+ disabled={loading}
116
+ style={{ flex: 1 }}
117
+ >
118
+ Cancel
119
+ </button>
120
+ </div>
121
+ </form>
122
+ )
123
+ )}
124
+ </main>
125
+ );
126
+ }