easy-starter 2.0.0 → 2.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.
- package/README.md +5 -4
- package/bin/index.js +65 -38
- package/package.json +1 -1
- package/template/.cursorrules +32 -7
- package/template/README.md +48 -1
- package/template/gitignore +1 -0
- package/template/index.html +3 -3
- package/template/package.json +11 -4
- package/template/public/site.webmanifest +8 -8
- package/template/scripts/deploy.ts +3 -2
- package/template/server/index.tsx +42 -30
- package/template/src/Router.tsx +2 -2
- package/template/src/components/Img.tsx +127 -0
- package/template/src/pages/Index.tsx +4 -13
- package/template/src/pages/NotFound.tsx +1 -1
- package/template/src/pages/ResetPassword.tsx +6 -8
- package/template/src/pages/admin/Admin.tsx +31 -59
- package/template/src/pages/admin/AnalyticsTab.tsx +4 -4
- package/template/src/pages/admin/ErrorsTab.tsx +13 -13
- package/template/src/pages/admin/UsersTab.tsx +15 -22
- package/template/src/services/common.ts +11 -1
- package/template/src/styles.css +207 -0
- package/template/tsconfig.json +5 -1
- package/template/types.ts +4 -4
- package/template/vite.config.ts +7 -2
- package/template/public/images/icon-16.png +0 -0
- package/template/public/images/icon-180.png +0 -0
- package/template/public/images/icon-192.png +0 -0
- package/template/public/images/icon-32.png +0 -0
- package/template/public/images/icon-512.png +0 -0
|
@@ -25,6 +25,10 @@ import { UserProfile } from "../types";
|
|
|
25
25
|
import puppeteer from "puppeteer";
|
|
26
26
|
// --- OGIMAGE END ---
|
|
27
27
|
|
|
28
|
+
// --- RESIZER START ---
|
|
29
|
+
import { expressImageResizer } from "easy-image-resizer";
|
|
30
|
+
// --- RESIZER END ---
|
|
31
|
+
|
|
28
32
|
import App from "../src/App";
|
|
29
33
|
import { API, Database } from "../types";
|
|
30
34
|
|
|
@@ -69,21 +73,21 @@ const authServer = new EasyLoginServer<UserProfile>({
|
|
|
69
73
|
jwtSecret: process.env.JWT_SECRET || "fallback-secret-key-change-me",
|
|
70
74
|
secureCookies: process.env.NODE_ENV === "production",
|
|
71
75
|
db: {
|
|
72
|
-
insertUser: async (user) => insert("user", user),
|
|
73
|
-
getUserById: async (id) => select("user", id),
|
|
74
|
-
getUserByMail: async (mail) => {
|
|
76
|
+
insertUser: async (user: any) => insert("user", user),
|
|
77
|
+
getUserById: async (id: string) => select("user", id),
|
|
78
|
+
getUserByMail: async (mail: string) => {
|
|
75
79
|
const users = await selectArray("user");
|
|
76
|
-
return users.find(u => u.mail.toLowerCase() === mail.toLowerCase()) || null;
|
|
80
|
+
return users.find((u: any) => u.mail.toLowerCase() === mail.toLowerCase()) || null;
|
|
77
81
|
},
|
|
78
|
-
getUserByRecoveryToken: async (token) => {
|
|
82
|
+
getUserByRecoveryToken: async (token: string) => {
|
|
79
83
|
const users = await selectArray("user");
|
|
80
|
-
return users.find(u => u.passwordRecovery?.token === token) || null;
|
|
84
|
+
return users.find((u: any) => u.passwordRecovery?.token === token) || null;
|
|
81
85
|
},
|
|
82
|
-
updateUser: async (id, user) => {
|
|
86
|
+
updateUser: async (id: string, user: any) => {
|
|
83
87
|
await update("user", id, user);
|
|
84
88
|
},
|
|
85
89
|
},
|
|
86
|
-
mailSender: async (lang, mailTo, token) => {
|
|
90
|
+
mailSender: async (lang: string, mailTo: string, token: string) => {
|
|
87
91
|
await sendMailForgottenPassword(lang, mailTo, token, ORIGIN);
|
|
88
92
|
},
|
|
89
93
|
});
|
|
@@ -92,6 +96,14 @@ const authServer = new EasyLoginServer<UserProfile>({
|
|
|
92
96
|
authServer.registerExpressRoutes(app);
|
|
93
97
|
// --- AUTH END ---
|
|
94
98
|
|
|
99
|
+
// --- RESIZER START ---
|
|
100
|
+
// Serve and dynamically resize images from public directory with auto-caching
|
|
101
|
+
app.use(expressImageResizer({
|
|
102
|
+
sourceDir: "./public",
|
|
103
|
+
cacheDir: "./cache/images",
|
|
104
|
+
}));
|
|
105
|
+
// --- RESIZER END ---
|
|
106
|
+
|
|
95
107
|
// Serve static files generated by the bundler (JS, CSS)
|
|
96
108
|
app.use(express.static("dist"));
|
|
97
109
|
// Serve any public assets (like images, fonts)
|
|
@@ -135,7 +147,7 @@ app.get("/og-image.png", async (req, res) => {
|
|
|
135
147
|
await page.setJavaScriptEnabled(true);
|
|
136
148
|
// --- NO_SSR END ---
|
|
137
149
|
await page.setViewport({ width: 600, height: 500 });
|
|
138
|
-
|
|
150
|
+
|
|
139
151
|
// Render target page
|
|
140
152
|
await page.goto(`${LOCALHOST}${path}`, { waitUntil: 'networkidle0' });
|
|
141
153
|
const base64 = await page.screenshot({ type: "png", encoding: "base64" });
|
|
@@ -213,16 +225,16 @@ setAPIBackend<API>(app, {
|
|
|
213
225
|
return item;
|
|
214
226
|
},
|
|
215
227
|
async addItem({ name, value }) {
|
|
216
|
-
const id = await insert("item", { name, value });
|
|
228
|
+
const id = await insert("item", { name, value } as any);
|
|
217
229
|
return id;
|
|
218
230
|
},
|
|
219
231
|
async removeItem({ id }) {
|
|
220
232
|
await remove("item", id);
|
|
221
233
|
return true;
|
|
222
234
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
async getEasyAnalyticsData({ password, month }) {
|
|
235
|
+
|
|
236
|
+
/* --- ADMIN_NO_AUTH START --- */
|
|
237
|
+
async getEasyAnalyticsData({ password, month }: { password?: string; month: string }) {
|
|
226
238
|
const validPassword = process.env.ADMIN_PASSWORD;
|
|
227
239
|
if (!validPassword || password !== validPassword) {
|
|
228
240
|
throw new Error("Invalid password or server error");
|
|
@@ -234,10 +246,10 @@ setAPIBackend<API>(app, {
|
|
|
234
246
|
return [];
|
|
235
247
|
// --- NO_ANALYTICS END ---
|
|
236
248
|
},
|
|
237
|
-
|
|
249
|
+
/* --- ADMIN_NO_AUTH END --- */
|
|
238
250
|
|
|
239
251
|
// --- ADMIN_WITH_AUTH START ---
|
|
240
|
-
async getEasyAnalyticsData({ month }, req, res) {
|
|
252
|
+
async getEasyAnalyticsData({ month }: { month: string }, req?: any, res?: any) {
|
|
241
253
|
const session = await authServer.checkLogin(req!, res!);
|
|
242
254
|
const user = await select("user", session.userId);
|
|
243
255
|
if (!user || user.role !== "admin") {
|
|
@@ -252,43 +264,43 @@ setAPIBackend<API>(app, {
|
|
|
252
264
|
},
|
|
253
265
|
// --- ADMIN_WITH_AUTH END ---
|
|
254
266
|
|
|
255
|
-
|
|
256
|
-
async getEasyAnalyticsErrors({ password, month }) {
|
|
267
|
+
/* --- ADMIN_ERRORS_NO_AUTH START --- */
|
|
268
|
+
async getEasyAnalyticsErrors({ password, month }: { password?: string; month: string }) {
|
|
257
269
|
const validPassword = process.env.ADMIN_PASSWORD;
|
|
258
270
|
if (!validPassword || password !== validPassword) {
|
|
259
271
|
throw new Error("Invalid password or server error");
|
|
260
272
|
}
|
|
261
|
-
return await selectArray(`easyAnalyticsError-${month}`);
|
|
273
|
+
return await selectArray(`easyAnalyticsError-${month}` as any);
|
|
262
274
|
},
|
|
263
|
-
|
|
275
|
+
/* --- ADMIN_ERRORS_NO_AUTH END --- */
|
|
264
276
|
|
|
265
277
|
// --- ADMIN_ERRORS_WITH_AUTH START ---
|
|
266
|
-
async getEasyAnalyticsErrors({ month }, req, res) {
|
|
278
|
+
async getEasyAnalyticsErrors({ month }: { month: string }, req?: any, res?: any) {
|
|
267
279
|
const session = await authServer.checkLogin(req!, res!);
|
|
268
280
|
const user = await select("user", session.userId);
|
|
269
281
|
if (!user || user.role !== "admin") {
|
|
270
282
|
throw new Error("Forbidden");
|
|
271
283
|
}
|
|
272
|
-
return await selectArray(`easyAnalyticsError-${month}`);
|
|
284
|
+
return await selectArray(`easyAnalyticsError-${month}` as any);
|
|
273
285
|
},
|
|
274
286
|
// --- ADMIN_ERRORS_WITH_AUTH END ---
|
|
275
287
|
|
|
276
288
|
// --- ADMIN_USERS START ---
|
|
277
|
-
async getUsers(
|
|
289
|
+
async getUsers(_params: {}, req: any, res: any) {
|
|
278
290
|
const session = await authServer.checkLogin(req!, res!);
|
|
279
291
|
const user = await select("user", session.userId);
|
|
280
292
|
if (!user || user.role !== "admin") {
|
|
281
293
|
throw new Error("Forbidden");
|
|
282
294
|
}
|
|
283
295
|
const allUsers = await selectArray("user");
|
|
284
|
-
return allUsers.map(u => ({
|
|
296
|
+
return allUsers.map((u: any) => ({
|
|
285
297
|
_id: u._id,
|
|
286
298
|
email: u.mail,
|
|
287
299
|
name: u.name || u.profile?.name || "",
|
|
288
300
|
role: u.profile?.role || u.role || "user"
|
|
289
301
|
}));
|
|
290
302
|
},
|
|
291
|
-
async updateUserRole({ userId, role }, req, res) {
|
|
303
|
+
async updateUserRole({ userId, role }: { userId: string; role: "admin" | "user" }, req: any, res: any) {
|
|
292
304
|
const session = await authServer.checkLogin(req!, res!);
|
|
293
305
|
const user = await select("user", session.userId);
|
|
294
306
|
if (!user || user.role !== "admin") {
|
|
@@ -299,8 +311,8 @@ setAPIBackend<API>(app, {
|
|
|
299
311
|
throw new Error("User not found");
|
|
300
312
|
}
|
|
301
313
|
targetUser.role = role;
|
|
302
|
-
targetUser.profile = targetUser.profile || { name: targetUser.name || "", role: "user" };
|
|
303
|
-
targetUser.profile.role = role;
|
|
314
|
+
(targetUser as any).profile = (targetUser as any).profile || { name: (targetUser as any).name || "", role: "user" };
|
|
315
|
+
(targetUser as any).profile.role = role;
|
|
304
316
|
await update("user", userId, targetUser);
|
|
305
317
|
return true;
|
|
306
318
|
},
|
|
@@ -308,12 +320,12 @@ setAPIBackend<API>(app, {
|
|
|
308
320
|
});
|
|
309
321
|
|
|
310
322
|
// --------------------------------- SEO ---------------------------------------
|
|
311
|
-
app.get("/robots.txt", (_, res) => {
|
|
323
|
+
app.get("/robots.txt", (_: express.Request, res: express.Response) => {
|
|
312
324
|
res.type("text/plain");
|
|
313
325
|
res.send(`User-agent: *\nAllow: /\n\nSitemap: ${ORIGIN}/sitemap.xml`);
|
|
314
326
|
});
|
|
315
327
|
|
|
316
|
-
app.get("/sitemap.xml", async (_, res) => {
|
|
328
|
+
app.get("/sitemap.xml", async (_: express.Request, res: express.Response) => {
|
|
317
329
|
const sitemap = [{
|
|
318
330
|
url: ORIGIN + "/",
|
|
319
331
|
lastModified: new Date().toISOString().slice(0, 10),
|
|
@@ -333,7 +345,7 @@ app.get("/sitemap.xml", async (_, res) => {
|
|
|
333
345
|
|
|
334
346
|
// --------------------------------- SSR ---------------------------------------
|
|
335
347
|
// --- SSR START ---
|
|
336
|
-
app.get(/.*/, async (req, res) => {
|
|
348
|
+
app.get(/.*/, async (req: express.Request, res: express.Response) => {
|
|
337
349
|
try {
|
|
338
350
|
const indexHtml = await readFile(INDEX_HTML_PATH, "utf-8");
|
|
339
351
|
const html = await renderToHTML(ORIGIN, req.url, indexHtml, <App />);
|
|
@@ -346,7 +358,7 @@ app.get(/.*/, async (req, res) => {
|
|
|
346
358
|
});
|
|
347
359
|
// --- SSR END ---
|
|
348
360
|
// --- NO_SSR START ---
|
|
349
|
-
app.get(/.*/, async (req, res) => {
|
|
361
|
+
app.get(/.*/, async (req: express.Request, res: express.Response) => {
|
|
350
362
|
res.sendFile(INDEX_HTML_PATH);
|
|
351
363
|
});
|
|
352
364
|
// --- NO_SSR END ---
|
package/template/src/Router.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import ResetPassword from "./pages/ResetPassword";
|
|
|
15
15
|
|
|
16
16
|
export default function AppRouter() {
|
|
17
17
|
return <Router
|
|
18
|
-
renderPage={({ path }) => {
|
|
18
|
+
renderPage={({ path }: { path: string[] }) => {
|
|
19
19
|
// The 'path' variable is an array of strings representing the URL segments.
|
|
20
20
|
// For example, "/users/123" would be ["users", "123"].
|
|
21
21
|
|
|
@@ -24,7 +24,7 @@ export default function AppRouter() {
|
|
|
24
24
|
|
|
25
25
|
// --- ADMIN START ---
|
|
26
26
|
if (path[0] === 'admin') return (
|
|
27
|
-
<Suspense fallback={<div
|
|
27
|
+
<Suspense fallback={<div className="loading-fallback">Loading admin...</div>}>
|
|
28
28
|
<Admin path={path} />
|
|
29
29
|
</Suspense>
|
|
30
30
|
);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export type ImgProps = React.ImgHTMLAttributes<HTMLImageElement> & {
|
|
4
|
+
/** Target width in pixels */
|
|
5
|
+
w?: number | string;
|
|
6
|
+
/** Target height in pixels */
|
|
7
|
+
h?: number | string;
|
|
8
|
+
/** Max width in pixels */
|
|
9
|
+
mw?: number | string;
|
|
10
|
+
/** Max height in pixels */
|
|
11
|
+
mh?: number | string;
|
|
12
|
+
/** Max width in pixels (alias) */
|
|
13
|
+
maxWidth?: number | string;
|
|
14
|
+
/** Max height in pixels (alias) */
|
|
15
|
+
maxHeight?: number | string;
|
|
16
|
+
/** Output image format (default: "webp") */
|
|
17
|
+
format?: "webp" | "jpeg" | "png";
|
|
18
|
+
/** Output image format (alias) */
|
|
19
|
+
type?: "webp" | "jpeg" | "png";
|
|
20
|
+
/** Quality from 1 to 100 */
|
|
21
|
+
quality?: number;
|
|
22
|
+
/** Quality from 1 to 100 (alias) */
|
|
23
|
+
q?: number;
|
|
24
|
+
/** Smooth resize processing */
|
|
25
|
+
smooth?: boolean;
|
|
26
|
+
/** Smooth resize processing (alias) */
|
|
27
|
+
s?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function imgSrc(
|
|
31
|
+
src: string,
|
|
32
|
+
options: {
|
|
33
|
+
w?: number | string;
|
|
34
|
+
h?: number | string;
|
|
35
|
+
mw?: number | string;
|
|
36
|
+
mh?: number | string;
|
|
37
|
+
format?: "webp" | "jpeg" | "png";
|
|
38
|
+
quality?: number;
|
|
39
|
+
smooth?: boolean;
|
|
40
|
+
}
|
|
41
|
+
): string | undefined {
|
|
42
|
+
if (!src) return src;
|
|
43
|
+
|
|
44
|
+
const targetFormat = options.format || "webp";
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const [base, queryString] = src.split("?");
|
|
48
|
+
const params = new URLSearchParams(queryString || "");
|
|
49
|
+
|
|
50
|
+
if (options.w !== undefined && !params.has("w") && !params.has("width")) {
|
|
51
|
+
params.set("w", String(options.w));
|
|
52
|
+
}
|
|
53
|
+
if (options.h !== undefined && !params.has("h") && !params.has("height")) {
|
|
54
|
+
params.set("h", String(options.h));
|
|
55
|
+
}
|
|
56
|
+
if (options.mw !== undefined && !params.has("mw") && !params.has("maxWidth")) {
|
|
57
|
+
params.set("mw", String(options.mw));
|
|
58
|
+
}
|
|
59
|
+
if (options.mh !== undefined && !params.has("mh") && !params.has("maxHeight")) {
|
|
60
|
+
params.set("mh", String(options.mh));
|
|
61
|
+
}
|
|
62
|
+
if (options.quality !== undefined && !params.has("q") && !params.has("quality")) {
|
|
63
|
+
params.set("q", String(options.quality));
|
|
64
|
+
}
|
|
65
|
+
if (options.smooth !== undefined && !params.has("s") && !params.has("smooth")) {
|
|
66
|
+
params.set("s", String(options.smooth));
|
|
67
|
+
}
|
|
68
|
+
if (targetFormat && !params.has("format") && !params.has("type") && !params.has("ext")) {
|
|
69
|
+
params.set("format", targetFormat);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const finalQuery = params.toString();
|
|
73
|
+
return finalQuery ? `${base}?${finalQuery}` : base;
|
|
74
|
+
} catch {
|
|
75
|
+
return src;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Smart image component wrapping HTML <img> with automatic easy-image-resizer URL formatting.
|
|
81
|
+
* Defaults to WebP format with optional width/height/quality scaling options.
|
|
82
|
+
*/
|
|
83
|
+
export function Img({
|
|
84
|
+
src,
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
w,
|
|
88
|
+
h,
|
|
89
|
+
mw,
|
|
90
|
+
mh,
|
|
91
|
+
maxWidth,
|
|
92
|
+
maxHeight,
|
|
93
|
+
format,
|
|
94
|
+
type,
|
|
95
|
+
quality,
|
|
96
|
+
q,
|
|
97
|
+
smooth,
|
|
98
|
+
s,
|
|
99
|
+
...restProps
|
|
100
|
+
}: ImgProps) {
|
|
101
|
+
const finalW = w ?? width;
|
|
102
|
+
const finalH = h ?? height;
|
|
103
|
+
const finalMw = mw ?? maxWidth;
|
|
104
|
+
const finalMh = mh ?? maxHeight;
|
|
105
|
+
const finalFormat = format ?? type ?? "webp";
|
|
106
|
+
const finalQuality = q ?? quality;
|
|
107
|
+
const finalSmooth = s ?? smooth;
|
|
108
|
+
|
|
109
|
+
const finalSrc = imgSrc(src || "", {
|
|
110
|
+
w: finalW,
|
|
111
|
+
h: finalH,
|
|
112
|
+
mw: finalMw,
|
|
113
|
+
mh: finalMh,
|
|
114
|
+
format: finalFormat,
|
|
115
|
+
quality: finalQuality,
|
|
116
|
+
smooth: finalSmooth,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return <img
|
|
120
|
+
src={finalSrc}
|
|
121
|
+
width={finalW}
|
|
122
|
+
height={finalH}
|
|
123
|
+
{...restProps}
|
|
124
|
+
/>;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default Img;
|
|
@@ -12,18 +12,9 @@ import { UserProfile } from "../../types";
|
|
|
12
12
|
// --- AUTH END ---
|
|
13
13
|
|
|
14
14
|
export default function Index() {
|
|
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
15
|
useHead({
|
|
22
16
|
title: "Welcome to easy-starter",
|
|
23
17
|
description: "The ultimate foundation for your next project."
|
|
24
|
-
// --- OGIMAGE START ---
|
|
25
|
-
, image: ogImage
|
|
26
|
-
// --- OGIMAGE END ---
|
|
27
18
|
});
|
|
28
19
|
|
|
29
20
|
// --- AUTH START ---
|
|
@@ -69,9 +60,9 @@ export default function Index() {
|
|
|
69
60
|
return (
|
|
70
61
|
<main>
|
|
71
62
|
{/* --- AUTH START --- */}
|
|
72
|
-
<div
|
|
63
|
+
<div className="auth-bar">
|
|
73
64
|
{isLoggedIn ? (
|
|
74
|
-
<div
|
|
65
|
+
<div className="auth-user-info">
|
|
75
66
|
<span>Logged in as: <strong>{user.name} ({user.role})</strong></span>
|
|
76
67
|
<button onClick={() => loginUser(null)}>Log Out</button>
|
|
77
68
|
</div>
|
|
@@ -85,9 +76,9 @@ export default function Index() {
|
|
|
85
76
|
<figure></figure>
|
|
86
77
|
<h1>Welcome to easy-starter!</h1>
|
|
87
78
|
<p>The ultimate foundation for your next project.</p>
|
|
88
|
-
|
|
79
|
+
|
|
89
80
|
{/* --- ADMIN START --- */}
|
|
90
|
-
<div
|
|
81
|
+
<div className="admin-link-wrapper">
|
|
91
82
|
<Link href="/admin">
|
|
92
83
|
→ Go to Admin Dashboard
|
|
93
84
|
</Link>
|
|
@@ -6,7 +6,7 @@ export default function NotFound() {
|
|
|
6
6
|
useHead({ title: "404 Not Found", description: "Page you are looking for does not exist." });
|
|
7
7
|
|
|
8
8
|
return (
|
|
9
|
-
<div
|
|
9
|
+
<div className="not-found">
|
|
10
10
|
<h1>404</h1>
|
|
11
11
|
<p>Oops, page not found.</p>
|
|
12
12
|
<Link href="/">Go back home</Link>
|
|
@@ -58,15 +58,15 @@ export default function ResetPasswordPage() {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
|
-
<main
|
|
62
|
-
<h2 className="easy-user-auth-title"
|
|
61
|
+
<main className="reset-password-main">
|
|
62
|
+
<h2 className="easy-user-auth-title">🔑 {dict.userForgottenPasswordChange}</h2>
|
|
63
63
|
|
|
64
64
|
{error && !token && (
|
|
65
|
-
<div className="easy-user-auth-alert"
|
|
65
|
+
<div className="easy-user-auth-alert">{error}</div>
|
|
66
66
|
)}
|
|
67
67
|
|
|
68
68
|
{success ? (
|
|
69
|
-
<div className="easy-user-auth-alert
|
|
69
|
+
<div className="easy-user-auth-alert alert-success">
|
|
70
70
|
{dict.userForgottenPasswordSuccessChange}
|
|
71
71
|
</div>
|
|
72
72
|
) : (
|
|
@@ -97,14 +97,13 @@ export default function ResetPasswordPage() {
|
|
|
97
97
|
</div>
|
|
98
98
|
|
|
99
99
|
{error && <div className="easy-user-auth-alert">{error}</div>}
|
|
100
|
-
{validationError && <p className="easy-user-auth-info
|
|
100
|
+
{validationError && <p className="easy-user-auth-info text-error">{validationError}</p>}
|
|
101
101
|
|
|
102
|
-
<div
|
|
102
|
+
<div className="btn-group">
|
|
103
103
|
<button
|
|
104
104
|
type="submit"
|
|
105
105
|
className="easy-user-auth-submit-btn"
|
|
106
106
|
disabled={!!validationError || !password || !passwordAgain || loading}
|
|
107
|
-
style={{ flex: 1 }}
|
|
108
107
|
>
|
|
109
108
|
{loading ? "Updating..." : dict.change}
|
|
110
109
|
</button>
|
|
@@ -113,7 +112,6 @@ export default function ResetPasswordPage() {
|
|
|
113
112
|
className="easy-user-auth-back-btn"
|
|
114
113
|
onClick={() => push("/")}
|
|
115
114
|
disabled={loading}
|
|
116
|
-
style={{ flex: 1 }}
|
|
117
115
|
>
|
|
118
116
|
Cancel
|
|
119
117
|
</button>
|