afront 1.0.18 → 1.0.20
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/build-prod/index.html +1 -1
- package/build-prod/offline.html +1024 -0
- package/build-prod/service-worker.js +1 -0
- package/build-prod/static/css/main-b7c0f6b3ea505263e9a2.css +1 -0
- package/build-prod/static/js/3-b7f6c4256e55d2c173ac.js +1 -0
- package/build-prod/static/js/41-d729186c867a4a7f5f49.js +1 -0
- package/build-prod/static/js/525-506bbc7da1b98d130181.js +1 -0
- package/build-prod/static/js/573-8fcda455788ea4aa45cd.js +1 -0
- package/build-prod/static/js/99-ccf46a26c15904ecb674.js +1 -0
- package/build-prod/static/js/main-34c866ebbc6fbc291b85.js +1 -0
- package/build-prod-ssr/3.ssr.prod.js +1 -1
- package/build-prod-ssr/41.ssr.prod.js +1 -1
- package/build-prod-ssr/525.ssr.prod.js +1 -0
- package/build-prod-ssr/573.ssr.prod.js +1 -1
- package/build-prod-ssr/ssr.prod.js +1 -1
- package/build-prod-ssr/static/css/main-b7c0f6b3ea505263e9a2.css +1 -0
- package/build-prod-static/index.html +1 -1
- package/build-prod-static/offline.html +1024 -0
- package/build-prod-static/service-worker.js +1 -0
- package/build-prod-static/static/css/main-b7c0f6b3ea505263e9a2.css +1 -0
- package/build-prod-static/static/js/23-c9124d3cac34021e3406.js +1 -0
- package/build-prod-static/static/js/303-0d29064b1a912a7441ef.js +1 -0
- package/build-prod-static/static/js/636-adf7ca5d4e262a755df3.js +1 -0
- package/build-prod-static/static/js/863-2c35feb0663baeecb6f8.js +1 -0
- package/build-prod-static/static/js/main-02c8c189d5bec6c0a7f1.js +1 -0
- package/install.js +2 -2
- package/package.json +6 -5
- package/server.js +2 -0
- package/src/ARoutes/AFRoutes.js +22 -5
- package/src/Api/api.config.js +266 -0
- package/src/Api/login.service.js +44 -0
- package/src/App.js +10 -9
- package/src/Components/Loading/LoadingIndicator.js +12 -0
- package/src/Components/Loading/LoadingIndicator.module.css +34 -0
- package/src/Components/Loading/LoadingSpinner.js +27 -0
- package/src/Components/Loading/LoadingSpinner.module.css +100 -0
- package/src/Components/RequireAuth.js +30 -0
- package/src/PageNotFound.js +1 -1
- package/src/Pages/Signup.js +230 -0
- package/src/Routes/ARoutes.js +47 -6
- package/src/Routes/ARoutesStatic.js +83 -0
- package/src/Static/appStatic.js +10 -20
- package/src/Static/indexStatic.js +3 -0
- package/src/Utils/LoadingContext.js +5 -0
- package/src/index.js +0 -4
- package/webpack.build-prod.js +13 -0
- package/webpack.dev.js +18 -0
- package/webpack.prod.js +11 -2
- package/webpack.ssr.prod.js +4 -0
- package/build-prod/static/css/main-7f7c4e72ce002df48179.css +0 -1
- package/build-prod/static/js/3-985f8801e97b3b2025e2.js +0 -1
- package/build-prod/static/js/41-a66fae84fd39cfb1e349.js +0 -1
- package/build-prod/static/js/573-f7791eb491c75b0ccfb7.js +0 -1
- package/build-prod/static/js/main-1cbe42ef1eb3d942625c.js +0 -1
- package/build-prod-ssr/static/css/main-7f7c4e72ce002df48179.css +0 -1
- package/build-prod-static/static/css/main-7f7c4e72ce002df48179.css +0 -1
- package/build-prod-static/static/js/23-9e69ffb20f982ca1ba75.js +0 -1
- package/build-prod-static/static/js/303-1b6136e5efb4925c5f98.js +0 -1
- package/build-prod-static/static/js/636-7f7bf68f9765bc200b86.js +0 -1
- package/build-prod-static/static/js/main-6c6f40c44813930620ed.js +0 -1
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "afront",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "AFront is a front-end JavaScript library designed to create seamless server-side rendered (SSSR) websites.",
|
|
5
5
|
"main": "webpack.dev.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "webpack --config webpack.prod.js",
|
|
7
|
+
"build": "webpack --config webpack.prod.js && webpack --config webpack.post-prod.js",
|
|
8
8
|
"build:ssr": "webpack --config webpack.ssr.prod.js",
|
|
9
|
-
"build:alt": "webpack --config webpack.build-prod.js",
|
|
9
|
+
"build:alt": "webpack --config webpack.build-prod.js && webpack --config webpack.post-prod-static.js --stats-error-details",
|
|
10
10
|
"prod:ssr": "node build-prod-ssr/ssr.prod.js",
|
|
11
11
|
"start": "webpack serve --config webpack.dev.js",
|
|
12
12
|
"static": "node server.js"
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"asggen-headtags": "^1.0.3",
|
|
43
43
|
"babel-plugin-css-modules-transform": "^1.6.2",
|
|
44
44
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
|
45
|
+
"banner-webpack-after-content": "^1.0.3",
|
|
45
46
|
"copy-webpack-plugin": "^12.0.2",
|
|
46
47
|
"dotenv": "^16.4.5",
|
|
47
48
|
"express-rate-limit": "^7.4.1",
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"json5": "^1.0.2",
|
|
54
55
|
"loader-utils": "^1.4.2",
|
|
55
56
|
"postcss": "^8.4.31",
|
|
56
|
-
"react-router": "^7.
|
|
57
|
+
"react-router": "^7.5.2",
|
|
57
58
|
"readline-sync": "^1.4.10",
|
|
58
59
|
"styled-components": "^6.1.13",
|
|
59
60
|
"terser-webpack-plugin": "^5.3.10",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
"style-loader": "^4.0.0",
|
|
81
82
|
"webpack": "^5.96.1",
|
|
82
83
|
"webpack-cli": "^5.1.4",
|
|
83
|
-
"webpack-dev-server": "^5.1
|
|
84
|
+
"webpack-dev-server": "^5.2.1",
|
|
84
85
|
"webpack-node-externals": "^3.0.0"
|
|
85
86
|
},
|
|
86
87
|
"resolutions": {
|
package/server.js
CHANGED
package/src/ARoutes/AFRoutes.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { lazy } from "react";
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
const routes = [
|
|
5
|
-
{
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
{
|
|
5
|
+
path: "/",
|
|
6
|
+
element: lazy(() => import("../Pages/Home.js")),
|
|
7
|
+
withHeaderFooter: true,
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
path: "/signup",
|
|
11
|
+
element: lazy(() => import("../Pages/Signup.js")),
|
|
12
|
+
withHeaderFooter: false,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
path: "/support/s",
|
|
16
|
+
element: lazy(() => import("../Pages/Support.js")),
|
|
17
|
+
protected: true,
|
|
18
|
+
withHeaderFooter: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: "/*",
|
|
22
|
+
element: lazy(() => import("../PageNotFound.js")),
|
|
23
|
+
withHeaderFooter: true,
|
|
24
|
+
}, // Page Not Found route
|
|
8
25
|
// Add More Pages
|
|
9
26
|
];
|
|
10
27
|
|
|
11
|
-
export default routes;
|
|
28
|
+
export default routes;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Lightweight fetch-based API instance with interceptors and retry helper
|
|
2
|
+
const apiBase =
|
|
3
|
+
(typeof process !== "undefined" &&
|
|
4
|
+
process.env &&
|
|
5
|
+
process.env.REACT_APP_API_BASE) ||
|
|
6
|
+
(typeof window !== "undefined" &&
|
|
7
|
+
window.__env &&
|
|
8
|
+
window.__env.REACT_APP_API_BASE) ||
|
|
9
|
+
"http://localhost:4000/api/v1";
|
|
10
|
+
|
|
11
|
+
function isAbsoluteUrl(u) {
|
|
12
|
+
return /^https?:\/\//i.test(u);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildUrl(base, url, params) {
|
|
16
|
+
const full = isAbsoluteUrl(url) ? url : `${base}${url}`;
|
|
17
|
+
if (!params) return full;
|
|
18
|
+
const search = new URLSearchParams(params).toString();
|
|
19
|
+
return search ? `${full}${full.includes("?") ? "&" : "?"}${search}` : full;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createInstance({
|
|
23
|
+
baseURL = apiBase,
|
|
24
|
+
timeout = 60000,
|
|
25
|
+
headers = { "Content-Type": "application/json" },
|
|
26
|
+
withCredentials = true,
|
|
27
|
+
} = {}) {
|
|
28
|
+
const requestHandlers = [];
|
|
29
|
+
const responseHandlers = [];
|
|
30
|
+
|
|
31
|
+
const interceptors = {
|
|
32
|
+
request: {
|
|
33
|
+
use: (fn) => {
|
|
34
|
+
requestHandlers.push(fn);
|
|
35
|
+
return requestHandlers.length - 1;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
response: {
|
|
39
|
+
use: (onFulfilled, onRejected) => {
|
|
40
|
+
responseHandlers.push({ onFulfilled, onRejected });
|
|
41
|
+
return responseHandlers.length - 1;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
async function runRequestHandlers(cfg) {
|
|
47
|
+
let c = { ...cfg };
|
|
48
|
+
for (const h of requestHandlers) {
|
|
49
|
+
// handlers may be sync or async
|
|
50
|
+
// allow them to modify/return the config
|
|
51
|
+
// if they return undefined, assume they mutated in place
|
|
52
|
+
// otherwise use returned value
|
|
53
|
+
// eslint-disable-next-line no-await-in-loop
|
|
54
|
+
const out = await h(c);
|
|
55
|
+
if (typeof out !== "undefined") c = out;
|
|
56
|
+
}
|
|
57
|
+
return c;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runResponseHandlers(resOrErr, success = true) {
|
|
61
|
+
let acc = resOrErr;
|
|
62
|
+
for (const h of responseHandlers) {
|
|
63
|
+
try {
|
|
64
|
+
if (success && typeof h.onFulfilled === "function") {
|
|
65
|
+
// eslint-disable-next-line no-await-in-loop
|
|
66
|
+
const out = await h.onFulfilled(acc);
|
|
67
|
+
if (typeof out !== "undefined") acc = out;
|
|
68
|
+
} else if (!success && typeof h.onRejected === "function") {
|
|
69
|
+
// eslint-disable-next-line no-await-in-loop
|
|
70
|
+
const out = await h.onRejected(acc);
|
|
71
|
+
if (typeof out !== "undefined") acc = out;
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// if a handler throws, treat as rejection
|
|
75
|
+
acc = e;
|
|
76
|
+
success = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (success) return acc;
|
|
80
|
+
throw acc;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function request(cfg) {
|
|
84
|
+
const finalCfg = await runRequestHandlers({
|
|
85
|
+
baseURL,
|
|
86
|
+
timeout,
|
|
87
|
+
headers: { ...headers },
|
|
88
|
+
withCredentials,
|
|
89
|
+
...cfg,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const url = buildUrl(
|
|
93
|
+
finalCfg.baseURL || baseURL,
|
|
94
|
+
finalCfg.url || finalCfg.path || "/",
|
|
95
|
+
finalCfg.params
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const controller = new AbortController();
|
|
99
|
+
const to = finalCfg.timeout || timeout;
|
|
100
|
+
const timer = setTimeout(() => controller.abort(), to);
|
|
101
|
+
|
|
102
|
+
const fetchOpts = {
|
|
103
|
+
method: (finalCfg.method || "GET").toUpperCase(),
|
|
104
|
+
headers: finalCfg.headers || {},
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
};
|
|
107
|
+
if (finalCfg.withCredentials || finalCfg.withCredentials === undefined)
|
|
108
|
+
fetchOpts.credentials = "include";
|
|
109
|
+
if (finalCfg.body !== undefined && finalCfg.body !== null) {
|
|
110
|
+
fetchOpts.body = finalCfg.body;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// log request for convenience
|
|
115
|
+
// allow user interceptors to already log as requested
|
|
116
|
+
const raw = await fetch(url, fetchOpts);
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
|
|
119
|
+
// try to parse JSON, fallback to text
|
|
120
|
+
let data = null;
|
|
121
|
+
const ct = raw.headers.get("content-type") || "";
|
|
122
|
+
if (ct.includes("application/json")) {
|
|
123
|
+
try {
|
|
124
|
+
data = await raw.json();
|
|
125
|
+
} catch (e) {
|
|
126
|
+
data = null;
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
try {
|
|
130
|
+
data = await raw.text();
|
|
131
|
+
} catch (e) {
|
|
132
|
+
data = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const response = { ok: raw.ok, status: raw.status, data, raw };
|
|
137
|
+
return await runResponseHandlers(response, true);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
// network or abort
|
|
141
|
+
return await runResponseHandlers(err, false);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// convenience methods
|
|
146
|
+
const instance = {
|
|
147
|
+
interceptors,
|
|
148
|
+
request,
|
|
149
|
+
get: (url, cfg = {}) => request({ method: "GET", url, ...cfg }),
|
|
150
|
+
post: (url, body, cfg = {}) =>
|
|
151
|
+
request({
|
|
152
|
+
method: "POST",
|
|
153
|
+
url,
|
|
154
|
+
body:
|
|
155
|
+
typeof body === "object" && !(body instanceof FormData)
|
|
156
|
+
? JSON.stringify(body)
|
|
157
|
+
: body,
|
|
158
|
+
headers: {
|
|
159
|
+
...(cfg.headers || {}),
|
|
160
|
+
"Content-Type":
|
|
161
|
+
body instanceof FormData ? undefined : "application/json",
|
|
162
|
+
},
|
|
163
|
+
...cfg,
|
|
164
|
+
}),
|
|
165
|
+
put: (url, body, cfg = {}) =>
|
|
166
|
+
request({
|
|
167
|
+
method: "PUT",
|
|
168
|
+
url,
|
|
169
|
+
body:
|
|
170
|
+
typeof body === "object" && !(body instanceof FormData)
|
|
171
|
+
? JSON.stringify(body)
|
|
172
|
+
: body,
|
|
173
|
+
headers: {
|
|
174
|
+
...(cfg.headers || {}),
|
|
175
|
+
"Content-Type":
|
|
176
|
+
body instanceof FormData ? undefined : "application/json",
|
|
177
|
+
},
|
|
178
|
+
...cfg,
|
|
179
|
+
}),
|
|
180
|
+
patch: (url, body, cfg = {}) =>
|
|
181
|
+
request({
|
|
182
|
+
method: "PATCH",
|
|
183
|
+
url,
|
|
184
|
+
body:
|
|
185
|
+
typeof body === "object" && !(body instanceof FormData)
|
|
186
|
+
? JSON.stringify(body)
|
|
187
|
+
: body,
|
|
188
|
+
headers: {
|
|
189
|
+
...(cfg.headers || {}),
|
|
190
|
+
"Content-Type":
|
|
191
|
+
body instanceof FormData ? undefined : "application/json",
|
|
192
|
+
},
|
|
193
|
+
...cfg,
|
|
194
|
+
}),
|
|
195
|
+
delete: (url, cfg = {}) => request({ method: "DELETE", url, ...cfg }),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return instance;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function requestWithRetry(
|
|
202
|
+
fn,
|
|
203
|
+
{ retries = 3, retryDelay = 1000, retryOn = (err) => true } = {}
|
|
204
|
+
) {
|
|
205
|
+
return new Promise(async (resolve, reject) => {
|
|
206
|
+
let attempt = 0;
|
|
207
|
+
while (attempt < retries) {
|
|
208
|
+
try {
|
|
209
|
+
// fn should be a function returning a promise
|
|
210
|
+
const res = await fn();
|
|
211
|
+
return resolve(res);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
attempt += 1;
|
|
214
|
+
if (attempt >= retries || !retryOn(err)) return reject(err);
|
|
215
|
+
// exponential backoff
|
|
216
|
+
// eslint-disable-next-line no-await-in-loop
|
|
217
|
+
await new Promise((r) => setTimeout(r, retryDelay * attempt));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
reject(new Error("Retries exhausted"));
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export const instance = createInstance({
|
|
225
|
+
baseURL: apiBase,
|
|
226
|
+
timeout: 60000,
|
|
227
|
+
headers: { "Content-Type": "application/json" },
|
|
228
|
+
withCredentials: true,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// expose defaults for logging/compatibility
|
|
232
|
+
instance.defaults = { baseURL: apiBase, timeout: 60000 };
|
|
233
|
+
|
|
234
|
+
// request logging interceptor
|
|
235
|
+
instance.interceptors.request.use((config) => {
|
|
236
|
+
try {
|
|
237
|
+
console.log(
|
|
238
|
+
`API Request -> ${(
|
|
239
|
+
(config.method || "").toString() || "GET"
|
|
240
|
+
).toUpperCase()} ${config.url || config.path} (base: ${
|
|
241
|
+
instance.defaults.baseURL
|
|
242
|
+
}) (timeout: ${config.timeout || instance.defaults.timeout}ms)`
|
|
243
|
+
);
|
|
244
|
+
} catch (e) {
|
|
245
|
+
/* ignore */
|
|
246
|
+
}
|
|
247
|
+
return config;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// response / error logging interceptor
|
|
251
|
+
instance.interceptors.response.use(
|
|
252
|
+
(response) => response,
|
|
253
|
+
(error) => {
|
|
254
|
+
if (!error || !error.status) {
|
|
255
|
+
console.error(
|
|
256
|
+
"Network or CORS error when calling API:",
|
|
257
|
+
error && error.message ? error.message : error
|
|
258
|
+
);
|
|
259
|
+
} else {
|
|
260
|
+
console.warn("API response error:", error.status, error.data);
|
|
261
|
+
}
|
|
262
|
+
return Promise.reject(error);
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
export default instance;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import api from "./api.config.js";
|
|
2
|
+
|
|
3
|
+
const BASE = "/auth";
|
|
4
|
+
|
|
5
|
+
export async function login({ email, password }) {
|
|
6
|
+
return api.post(
|
|
7
|
+
`${BASE}/login`,
|
|
8
|
+
{ email, password },
|
|
9
|
+
{ withCredentials: true }
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function signup({
|
|
14
|
+
email,
|
|
15
|
+
password,
|
|
16
|
+
name,
|
|
17
|
+
accountType,
|
|
18
|
+
companyName,
|
|
19
|
+
}) {
|
|
20
|
+
return api.post(
|
|
21
|
+
`${BASE}/signup`,
|
|
22
|
+
{ email, password, name, accountType, companyName },
|
|
23
|
+
{ withCredentials: true }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function checkSignup({ email }) {
|
|
28
|
+
return api.post(`${BASE}/check`, { email }, { withCredentials: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function logout() {
|
|
32
|
+
try {
|
|
33
|
+
await api.post(`${BASE}/logout`, null, { withCredentials: true });
|
|
34
|
+
} catch (e) {
|
|
35
|
+
/* ignore */
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
localStorage.setItem("auth-event", String(Date.now()));
|
|
39
|
+
} catch (e) {}
|
|
40
|
+
window.dispatchEvent(new Event("auth-changed"));
|
|
41
|
+
return { ok: true };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default { login, signup, logout, checkSignup };
|
package/src/App.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import ARoutes from "./Routes/ARoutes";
|
|
3
|
-
import
|
|
4
|
-
import Footer from "./Components/Footer/Footer.js";
|
|
3
|
+
import LoadingContext from "./Utils/LoadingContext";
|
|
5
4
|
|
|
6
5
|
const isProduction = process.env.NODE_ENV === "production";
|
|
7
6
|
|
|
@@ -9,20 +8,22 @@ const Tail = () => (
|
|
|
9
8
|
<asggen
|
|
10
9
|
style={{ display: "none" }}
|
|
11
10
|
dangerouslySetInnerHTML={{
|
|
12
|
-
__html: `Rendering Asggen DOM...</asggen></asggenapp></body></html>`,
|
|
11
|
+
__html: `Rendering Asggen DOM...</asggen></asggenapp><script>window.addEventListener('load', () => { const loadingElement = document.getElementById('loading'); if (loadingElement) { setTimeout(() => { loadingElement.style.display = 'none'; }, 00);}});</script><script>if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker .register("/service-worker.js") .then((registration) => { registration.scope }) .catch((error) => { console.error("Service Worker registration failed:", error); }); }); }</script></body></html>`,
|
|
13
12
|
}}
|
|
14
13
|
/>
|
|
15
14
|
);
|
|
16
15
|
|
|
17
16
|
function App({ context }) {
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
18
|
return (
|
|
19
19
|
<>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
<LoadingContext.Provider value={{ loading, setLoading }}>
|
|
21
|
+
{loading && <LoadingIndicator />}
|
|
22
|
+
<ARoutes context={context} />
|
|
23
|
+
{isProduction ? <Tail /> : null}
|
|
24
|
+
</LoadingContext.Provider>
|
|
24
25
|
</>
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export default App;
|
|
29
|
+
export default App;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as styles from "./LoadingIndicator.module.css";
|
|
3
|
+
|
|
4
|
+
const LoadingIndicator = () => {
|
|
5
|
+
return (
|
|
6
|
+
<div className={styles.loadingIndicator}>
|
|
7
|
+
<div className={styles.loadingLine}></div>
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default LoadingIndicator;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.loadingIndicator {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 0.25rem;
|
|
7
|
+
background-color: #4287f5;
|
|
8
|
+
z-index: 9998;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.loadingLine {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 100%;
|
|
15
|
+
background: linear-gradient(90deg, transparent, #84f9f1, transparent);
|
|
16
|
+
position: absolute;
|
|
17
|
+
animation: loadingAnimation 0.5s ease-in-out infinite;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@keyframes loadingAnimation {
|
|
21
|
+
0% {
|
|
22
|
+
transform: translateX(-100%);
|
|
23
|
+
opacity: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
50% {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
100% {
|
|
31
|
+
transform: translateX(100%);
|
|
32
|
+
opacity: 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as styles from "./LoadingSpinner.module.css";
|
|
3
|
+
|
|
4
|
+
const LoadingSpinner = ({ size }) => {
|
|
5
|
+
const scaleFactor = size / 80;
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
style={{ transform: `scale(${scaleFactor})` }}
|
|
9
|
+
className={styles.ldsSpinner}
|
|
10
|
+
>
|
|
11
|
+
<div></div>
|
|
12
|
+
<div></div>
|
|
13
|
+
<div></div>
|
|
14
|
+
<div></div>
|
|
15
|
+
<div></div>
|
|
16
|
+
<div></div>
|
|
17
|
+
<div></div>
|
|
18
|
+
<div></div>
|
|
19
|
+
<div></div>
|
|
20
|
+
<div></div>
|
|
21
|
+
<div></div>
|
|
22
|
+
<div></div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default LoadingSpinner;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
.ldsSpinner,
|
|
2
|
+
.ldsSpinner div,
|
|
3
|
+
.ldsSpinner div:after {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.ldsSpinner {
|
|
8
|
+
color: currentColor;
|
|
9
|
+
display: inline-block;
|
|
10
|
+
position: relative;
|
|
11
|
+
width: 80px;
|
|
12
|
+
height: 80px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ldsSpinner div {
|
|
16
|
+
transform-origin: 40px 40px;
|
|
17
|
+
animation: ldsSpinner 1.2s linear infinite;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ldsSpinner div:after {
|
|
21
|
+
content: " ";
|
|
22
|
+
display: block;
|
|
23
|
+
position: absolute;
|
|
24
|
+
top: 3.2px;
|
|
25
|
+
left: 36.8px;
|
|
26
|
+
width: 6.4px;
|
|
27
|
+
height: 17.6px;
|
|
28
|
+
border-radius: 20%;
|
|
29
|
+
background: currentColor;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ldsSpinner div:nth-child(1) {
|
|
33
|
+
transform: rotate(0deg);
|
|
34
|
+
animation-delay: -1.1s;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ldsSpinner div:nth-child(2) {
|
|
38
|
+
transform: rotate(30deg);
|
|
39
|
+
animation-delay: -1s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ldsSpinner div:nth-child(3) {
|
|
43
|
+
transform: rotate(60deg);
|
|
44
|
+
animation-delay: -0.9s;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ldsSpinner div:nth-child(4) {
|
|
48
|
+
transform: rotate(90deg);
|
|
49
|
+
animation-delay: -0.8s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ldsSpinner div:nth-child(5) {
|
|
53
|
+
transform: rotate(120deg);
|
|
54
|
+
animation-delay: -0.7s;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.ldsSpinner div:nth-child(6) {
|
|
58
|
+
transform: rotate(150deg);
|
|
59
|
+
animation-delay: -0.6s;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ldsSpinner div:nth-child(7) {
|
|
63
|
+
transform: rotate(180deg);
|
|
64
|
+
animation-delay: -0.5s;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.ldsSpinner div:nth-child(8) {
|
|
68
|
+
transform: rotate(210deg);
|
|
69
|
+
animation-delay: -0.4s;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.ldsSpinner div:nth-child(9) {
|
|
73
|
+
transform: rotate(240deg);
|
|
74
|
+
animation-delay: -0.3s;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ldsSpinner div:nth-child(10) {
|
|
78
|
+
transform: rotate(270deg);
|
|
79
|
+
animation-delay: -0.2s;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ldsSpinner div:nth-child(11) {
|
|
83
|
+
transform: rotate(300deg);
|
|
84
|
+
animation-delay: -0.1s;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ldsSpinner div:nth-child(12) {
|
|
88
|
+
transform: rotate(330deg);
|
|
89
|
+
animation-delay: 0s;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@keyframes ldsSpinner {
|
|
93
|
+
0% {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
100% {
|
|
98
|
+
opacity: 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { Navigate, Outlet, useLocation } from "react-router";
|
|
3
|
+
import auth from "../Api/login.service";
|
|
4
|
+
|
|
5
|
+
export default function RequireAuth({ children }) {
|
|
6
|
+
const location = useLocation();
|
|
7
|
+
const [status, setStatus] = useState("checking");
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
let mounted = true;
|
|
11
|
+
(async () => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await auth.getCurrentUser();
|
|
14
|
+
if (mounted && res?.data?.user) setStatus("authed");
|
|
15
|
+
else if (mounted) setStatus("unauth");
|
|
16
|
+
} catch (e) {
|
|
17
|
+
if (mounted) setStatus("unauth");
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
return () => {
|
|
21
|
+
mounted = false;
|
|
22
|
+
};
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
if (status === "checking") return null;
|
|
26
|
+
if (status === "unauth")
|
|
27
|
+
return <Navigate to="/login" state={{ from: location }} replace />;
|
|
28
|
+
// If children are provided, render them; otherwise render nested routes via Outlet
|
|
29
|
+
return children ? children : <Outlet />;
|
|
30
|
+
}
|
package/src/PageNotFound.js
CHANGED