Compare commits
9 Commits
58b4e2613c
...
c6b7736dac
Author | SHA1 | Date | |
---|---|---|---|
c6b7736dac | |||
fa5ab258da | |||
9dd06fa7bc | |||
bb7f846305 | |||
7f9563a2a6 | |||
d0d9c21aba | |||
44bc99dd9d | |||
ae338f88ee | |||
96903dec2b |
8
bun.lock
8
bun.lock
@ -23,7 +23,8 @@
|
||||
},
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "core",
|
||||
"name": "@cvsa/core",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@koshnic/ratelimit": "^1.0.3",
|
||||
"chalk": "^5.4.1",
|
||||
@ -34,6 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
},
|
||||
"packages/crawler": {
|
||||
@ -112,6 +114,8 @@
|
||||
|
||||
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
|
||||
|
||||
"@cvsa/core": ["@cvsa/core@workspace:packages/core"],
|
||||
|
||||
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
@ -528,8 +532,6 @@
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||
|
||||
"core": ["core@workspace:packages/core"],
|
||||
|
||||
"crawler": ["crawler@workspace:packages/crawler"],
|
||||
|
||||
"cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="],
|
||||
|
@ -4,7 +4,7 @@ import { SlidingWindow } from "@core/mq/slidingWindow.ts";
|
||||
import { getCaptchaConfigMaxDuration, getCurrentCaptchaDifficulty } from "@/lib/auth/captchaDifficulty.ts";
|
||||
import { sqlCred } from "@core/db/dbNew.ts";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
import { verify } from 'hono/jwt';
|
||||
import { verify } from "hono/jwt";
|
||||
import { JwtTokenInvalid, JwtTokenExpired } from "hono/utils/jwt/types";
|
||||
import { getJWTsecret } from "@/lib/auth/getJWTsecret.ts";
|
||||
import { lockManager } from "@core/mq/lockManager.ts";
|
||||
@ -23,7 +23,8 @@ export const captchaMiddleware = async (c: Context, next: Next) => {
|
||||
if (!authHeader) {
|
||||
const response: ErrorResponse = {
|
||||
message: "'Authorization' header is missing.",
|
||||
code: "UNAUTHORIZED"
|
||||
code: "UNAUTHORIZED",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 401);
|
||||
}
|
||||
@ -32,7 +33,8 @@ export const captchaMiddleware = async (c: Context, next: Next) => {
|
||||
if (!authIsBearer || authHeader.length < 8) {
|
||||
const response: ErrorResponse = {
|
||||
message: "'Authorization' header is invalid.",
|
||||
code: "INVALID_HEADER"
|
||||
code: "INVALID_HEADER",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
}
|
||||
@ -60,47 +62,48 @@ export const captchaMiddleware = async (c: Context, next: Next) => {
|
||||
if (consumed) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Token has already been used.",
|
||||
code: "INVALID_CREDENTIALS"
|
||||
code: "INVALID_CREDENTIALS",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 401);
|
||||
}
|
||||
if (difficulty < requiredDifficulty) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Token too weak.",
|
||||
code: "UNAUTHORIZED"
|
||||
code: "UNAUTHORIZED",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 401);
|
||||
}
|
||||
const EXPIRE_FIVE_MINUTES = 300;
|
||||
await lockManager.acquireLock(tokenID, EXPIRE_FIVE_MINUTES);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
if (e instanceof JwtTokenInvalid) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Failed to verify the token.",
|
||||
code: "INVALID_CREDENTIALS"
|
||||
code: "INVALID_CREDENTIALS",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
}
|
||||
else if (e instanceof JwtTokenExpired) {
|
||||
} else if (e instanceof JwtTokenExpired) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Token expired.",
|
||||
code: "INVALID_CREDENTIALS"
|
||||
code: "INVALID_CREDENTIALS",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
}
|
||||
else if (e instanceof ValidationError) {
|
||||
} else if (e instanceof ValidationError) {
|
||||
const response: ErrorResponse = {
|
||||
code: "INVALID_QUERY_PARAMS",
|
||||
message: "Invalid query parameters",
|
||||
errors: e.errors
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const response: ErrorResponse = {
|
||||
message: "Unknown error.",
|
||||
code: "UNKNOWN_ERROR"
|
||||
code: "UNKNOWN_ERROR",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 500);
|
||||
}
|
||||
@ -114,4 +117,4 @@ export const captchaMiddleware = async (c: Context, next: Next) => {
|
||||
await window.event(`captcha-${identifierWithIP}`);
|
||||
|
||||
await next();
|
||||
};
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Hono } from "hono";
|
||||
import { Context, Hono } from "hono";
|
||||
import { Variables } from "hono/types";
|
||||
import { bodyLimitForPing } from "./bodyLimits.ts";
|
||||
import { pingHandler } from "routes/ping";
|
||||
@ -8,10 +8,21 @@ import { logger } from "./logger.ts";
|
||||
import { timing } from "hono/timing";
|
||||
import { contentType } from "./contentType.ts";
|
||||
import { captchaMiddleware } from "./captcha.ts";
|
||||
import { cors } from 'hono/cors';
|
||||
import { cors } from "hono/cors";
|
||||
|
||||
export function configureMiddleWares(app: Hono<{ Variables: Variables }>) {
|
||||
app.all("*", cors());
|
||||
app.use("*", async (c, next) => {
|
||||
if (c.req.path === "/user") {
|
||||
const corsMiddlewareHandler = cors({
|
||||
origin: c.req.header("Origin"),
|
||||
credentials: true
|
||||
});
|
||||
return corsMiddlewareHandler(c, next);
|
||||
}
|
||||
const corsMiddlewareHandler = cors();
|
||||
return corsMiddlewareHandler(c, next);
|
||||
});
|
||||
|
||||
app.use("*", contentType);
|
||||
app.use(timing());
|
||||
app.use("*", preetifyResponse);
|
||||
|
@ -6,8 +6,8 @@ import { RateLimiter } from "@koshnic/ratelimit";
|
||||
import { ErrorResponse } from "@/src/schema";
|
||||
import { redis } from "@core/db/redis.ts";
|
||||
|
||||
export const getIdentifier = (c: Context, includeIP: boolean = true) => {
|
||||
let ipAddr = generateRandomId(6);
|
||||
export const getUserIP = (c: Context) => {
|
||||
let ipAddr = null;
|
||||
const info = getConnInfo(c);
|
||||
if (info.remote && info.remote.address) {
|
||||
ipAddr = info.remote.address;
|
||||
@ -16,6 +16,14 @@ export const getIdentifier = (c: Context, includeIP: boolean = true) => {
|
||||
if (forwardedFor) {
|
||||
ipAddr = forwardedFor.split(",")[0];
|
||||
}
|
||||
return ipAddr;
|
||||
};
|
||||
|
||||
export const getIdentifier = (c: Context, includeIP: boolean = true) => {
|
||||
let ipAddr = generateRandomId(6);
|
||||
if (getUserIP(c)) {
|
||||
ipAddr = getUserIP(c);
|
||||
}
|
||||
const path = c.req.path;
|
||||
const method = c.req.method;
|
||||
const ipIdentifier = includeIP ? `@${ipAddr}` : "";
|
||||
@ -41,4 +49,4 @@ export const registerRateLimiter = async (c: Context<BlankEnv, "/user", {}>, nex
|
||||
}
|
||||
|
||||
await next();
|
||||
};
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { Context } from "hono";
|
||||
import { Bindings, BlankEnv } from "hono/types";
|
||||
import { ErrorResponse } from "src/schema";
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
import { sign } from 'hono/jwt'
|
||||
import { sign } from "hono/jwt";
|
||||
import { generateRandomId } from "@core/lib/randomID.ts";
|
||||
import { getJWTsecret } from "lib/auth/getJWTsecret.ts";
|
||||
|
||||
@ -34,7 +34,8 @@ export const verifyChallengeHandler = createHandlers(
|
||||
if (!ans) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Missing required query parameter: ans",
|
||||
code: "INVALID_QUERY_PARAMS"
|
||||
code: "INVALID_QUERY_PARAMS",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
}
|
||||
@ -43,26 +44,33 @@ export const verifyChallengeHandler = createHandlers(
|
||||
if (data.error && res.status === 404) {
|
||||
const response: ErrorResponse = {
|
||||
message: data.error,
|
||||
code: "ENTITY_NOT_FOUND"
|
||||
code: "ENTITY_NOT_FOUND",
|
||||
i18n: {
|
||||
key: "backend.error.captcha_not_found"
|
||||
},
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 401);
|
||||
} else if (data.error && res.status === 400) {
|
||||
const response: ErrorResponse = {
|
||||
message: data.error,
|
||||
code: "INVALID_QUERY_PARAMS"
|
||||
code: "INVALID_QUERY_PARAMS",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
} else if (data.error) {
|
||||
const response: ErrorResponse = {
|
||||
message: data.error,
|
||||
code: "UNKNOWN_ERROR"
|
||||
code: "UNKNOWN_ERROR",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 500);
|
||||
}
|
||||
if (!data.success) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Incorrect answer",
|
||||
code: "INVALID_CREDENTIALS"
|
||||
code: "INVALID_CREDENTIALS",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 401);
|
||||
}
|
||||
@ -74,13 +82,16 @@ export const verifyChallengeHandler = createHandlers(
|
||||
const jwtSecret = r as string;
|
||||
|
||||
const tokenID = generateRandomId(6);
|
||||
const NOW = Math.floor(Date.now() / 1000)
|
||||
const NOW = Math.floor(Date.now() / 1000);
|
||||
const FIVE_MINUTES_LATER = NOW + 60 * 5;
|
||||
const jwt = await sign({
|
||||
difficulty: data.difficulty!,
|
||||
id: tokenID,
|
||||
exp: FIVE_MINUTES_LATER
|
||||
}, jwtSecret);
|
||||
const jwt = await sign(
|
||||
{
|
||||
difficulty: data.difficulty!,
|
||||
id: tokenID,
|
||||
exp: FIVE_MINUTES_LATER
|
||||
},
|
||||
jwtSecret
|
||||
);
|
||||
return c.json({
|
||||
token: jwt
|
||||
});
|
||||
|
@ -1,2 +1,2 @@
|
||||
export * from "./session/POST.ts";
|
||||
export * from "./[id]/result/GET.ts";
|
||||
export * from "./[id]/result/GET.ts";
|
||||
|
@ -2,58 +2,50 @@ import { createHandlers } from "src/utils.ts";
|
||||
import { getCurrentCaptchaDifficulty } from "@/lib/auth/captchaDifficulty.ts";
|
||||
import { sqlCred } from "@core/db/dbNew.ts";
|
||||
import { object, string, ValidationError } from "yup";
|
||||
import { ErrorResponse } from "@/src/schema";
|
||||
import { CaptchaSessionResponse, ErrorResponse } from "@/src/schema";
|
||||
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
||||
|
||||
const bodySchema = object({
|
||||
route: string().matches(/(?:GET|POST|PUT|PATCH|DELETE)-\/.*/g)
|
||||
route: string().matches(/(?:GET|POST|PUT|PATCH|DELETE)-\/.*/g)
|
||||
});
|
||||
|
||||
interface CaptchaSessionResponse {
|
||||
success: boolean;
|
||||
id: string;
|
||||
g: string;
|
||||
n: string;
|
||||
t: number;
|
||||
}
|
||||
|
||||
const createNewChallenge = async (difficulty: number) => {
|
||||
const baseURL = process.env["UCAPTCHA_URL"];
|
||||
const url = new URL(baseURL);
|
||||
url.pathname = "/challenge";
|
||||
return await fetch(url.toString(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
difficulty: difficulty,
|
||||
})
|
||||
});
|
||||
}
|
||||
const baseURL = process.env["UCAPTCHA_URL"];
|
||||
const url = new URL(baseURL);
|
||||
url.pathname = "/challenge";
|
||||
return await fetch(url.toString(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
difficulty: difficulty
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
export const createCaptchaSessionHandler = createHandlers(async (c) => {
|
||||
try {
|
||||
const requestBody = await bodySchema.validate(await c.req.json());
|
||||
const { route } = requestBody;
|
||||
const difficuly = await getCurrentCaptchaDifficulty(sqlCred, route)
|
||||
const res = await createNewChallenge(difficuly);
|
||||
return c.json<CaptchaSessionResponse|unknown>(await res.json(), res.status as ContentfulStatusCode);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof ValidationError) {
|
||||
const response: ErrorResponse = {
|
||||
code: "INVALID_QUERY_PARAMS",
|
||||
message: "Invalid query parameters",
|
||||
errors: e.errors
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
} else {
|
||||
const response: ErrorResponse<unknown> = {
|
||||
code: "UNKNOWN_ERROR",
|
||||
message: "Unknown error",
|
||||
errors: [e]
|
||||
};
|
||||
return c.json<ErrorResponse<unknown>>(response, 500);
|
||||
}
|
||||
}
|
||||
try {
|
||||
const requestBody = await bodySchema.validate(await c.req.json());
|
||||
const { route } = requestBody;
|
||||
const difficuly = await getCurrentCaptchaDifficulty(sqlCred, route);
|
||||
const res = await createNewChallenge(difficuly);
|
||||
return c.json<CaptchaSessionResponse | unknown>(await res.json(), res.status as ContentfulStatusCode);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof ValidationError) {
|
||||
const response: ErrorResponse = {
|
||||
code: "INVALID_QUERY_PARAMS",
|
||||
message: "Invalid query parameters",
|
||||
errors: e.errors
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
} else {
|
||||
const response: ErrorResponse<unknown> = {
|
||||
code: "UNKNOWN_ERROR",
|
||||
message: "Unknown error",
|
||||
errors: [e]
|
||||
};
|
||||
return c.json<ErrorResponse<unknown>>(response, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
139
packages/backend/routes/user/POST.ts
Normal file
139
packages/backend/routes/user/POST.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
import Argon2id from "@rabbit-company/argon2id";
|
||||
import { object, string, ValidationError } from "yup";
|
||||
import type { Context } from "hono";
|
||||
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||
import { sqlCred } from "@core/db/dbNew.ts";
|
||||
import { ErrorResponse, SignUpResponse } from "src/schema";
|
||||
import { generateRandomId } from "@core/lib/randomID";
|
||||
import { getUserIP } from "@/middleware/rateLimiters";
|
||||
import { setCookie } from "hono/cookie";
|
||||
|
||||
const RegistrationBodySchema = object({
|
||||
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
||||
password: string().required("Password is required"),
|
||||
nickname: string().optional()
|
||||
});
|
||||
|
||||
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
||||
|
||||
export const userExists = async (username: string) => {
|
||||
const result = await sqlCred`
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE username = ${username}
|
||||
`;
|
||||
return result.length > 0;
|
||||
};
|
||||
|
||||
const createLoginSession = async (uid: number, ua?: string, ip?: string): Promise<string> => {
|
||||
const ip_address = ip || null;
|
||||
const user_agent = ua || null;
|
||||
const id = generateRandomId(24);
|
||||
await sqlCred`
|
||||
INSERT INTO login_sessions (id, uid, expire_at, ip_address, user_agent)
|
||||
VALUES (${id}, ${uid}, CURRENT_TIMESTAMP + INTERVAL '1 year', ${ip_address}, ${user_agent})
|
||||
`;
|
||||
return id;
|
||||
};
|
||||
|
||||
const getUserIDByName = async (username: string) => {
|
||||
const result = await sqlCred<{ id: number }[]>`
|
||||
SELECT id
|
||||
FROM users
|
||||
WHERE username = ${username}
|
||||
`;
|
||||
if (result.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return result[0].id;
|
||||
};
|
||||
|
||||
export const registerHandler = createHandlers(async (c: ContextType) => {
|
||||
try {
|
||||
const body = await RegistrationBodySchema.validate(await c.req.json());
|
||||
const { username, password, nickname } = body;
|
||||
|
||||
if (await userExists(username)) {
|
||||
const response: ErrorResponse = {
|
||||
message: `User "${username}" already exists.`,
|
||||
code: "ENTITY_EXISTS",
|
||||
errors: [],
|
||||
i18n: {
|
||||
key: "backend.error.user_exists",
|
||||
values: {
|
||||
username: username
|
||||
}
|
||||
}
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 400);
|
||||
}
|
||||
|
||||
const hash = await Argon2id.hashEncoded(password);
|
||||
|
||||
await sqlCred`
|
||||
INSERT INTO users (username, password, nickname)
|
||||
VALUES (${username}, ${hash}, ${nickname ? nickname : null})
|
||||
`;
|
||||
|
||||
const uid = await getUserIDByName(username);
|
||||
|
||||
if (!uid) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Cannot find registered user.",
|
||||
errors: [`Cannot find user ${username} in table 'users'.`],
|
||||
code: "ENTITY_NOT_FOUND",
|
||||
i18n: {
|
||||
key: "backend.error.user_not_found_after_register",
|
||||
values: {
|
||||
username: username
|
||||
}
|
||||
}
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 500);
|
||||
}
|
||||
|
||||
const sessionID = await createLoginSession(uid, c.req.header("User-Agent"), getUserIP(c));
|
||||
|
||||
const response: SignUpResponse = {
|
||||
username: username,
|
||||
token: sessionID
|
||||
};
|
||||
|
||||
const A_YEAR = 365 * 86400;
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
setCookie(c, "session_id", sessionID, {
|
||||
path: "/",
|
||||
maxAge: A_YEAR,
|
||||
domain: process.env.DOMAIN,
|
||||
secure: isDev ? false : true,
|
||||
sameSite: "Lax"
|
||||
});
|
||||
|
||||
return c.json<SignUpResponse>(response, 201);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid registration data.",
|
||||
errors: e.errors,
|
||||
code: "INVALID_PAYLOAD"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid JSON payload.",
|
||||
errors: [e.message],
|
||||
code: "INVALID_FORMAT"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Unknown error.",
|
||||
errors: [(e as Error).message],
|
||||
code: "UNKNOWN_ERROR"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 500);
|
||||
}
|
||||
}
|
||||
});
|
@ -1 +1,2 @@
|
||||
export * from "./register.ts";
|
||||
export * from "./POST.ts";
|
||||
export * from "./session/[id]/GET.ts";
|
||||
|
@ -1,74 +0,0 @@
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
import Argon2id from "@rabbit-company/argon2id";
|
||||
import { object, string, ValidationError } from "yup";
|
||||
import type { Context } from "hono";
|
||||
import type { Bindings, BlankEnv, BlankInput } from "hono/types";
|
||||
import { sqlCred } from "@core/db/dbNew.ts";
|
||||
import { ErrorResponse, StatusResponse } from "src/schema";
|
||||
|
||||
const RegistrationBodySchema = object({
|
||||
username: string().trim().required("Username is required").max(50, "Username cannot exceed 50 characters"),
|
||||
password: string().required("Password is required"),
|
||||
nickname: string().optional()
|
||||
});
|
||||
|
||||
type ContextType = Context<BlankEnv & { Bindings: Bindings }, "/user", BlankInput>;
|
||||
|
||||
export const userExists = async (username: string) => {
|
||||
const result = await sqlCred`
|
||||
SELECT 1
|
||||
FROM users
|
||||
WHERE username = ${username}
|
||||
`;
|
||||
return result.length > 0;
|
||||
};
|
||||
|
||||
export const registerHandler = createHandlers(async (c: ContextType) => {
|
||||
try {
|
||||
const body = await RegistrationBodySchema.validate(await c.req.json());
|
||||
const { username, password, nickname } = body;
|
||||
|
||||
if (await userExists(username)) {
|
||||
const response: StatusResponse = {
|
||||
message: `User "${username}" already exists.`
|
||||
};
|
||||
return c.json<StatusResponse>(response, 400);
|
||||
}
|
||||
|
||||
const hash = await Argon2id.hashEncoded(password);
|
||||
|
||||
await sqlCred`
|
||||
INSERT INTO users (username, password, nickname)
|
||||
VALUES (${username}, ${hash}, ${nickname ? nickname : null})
|
||||
`;
|
||||
|
||||
const response: StatusResponse = {
|
||||
message: `User '${username}' registered successfully.`
|
||||
};
|
||||
|
||||
return c.json<StatusResponse>(response, 201);
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid registration data.",
|
||||
errors: e.errors,
|
||||
code: "INVALID_PAYLOAD"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else if (e instanceof SyntaxError) {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Invalid JSON payload.",
|
||||
errors: [e.message],
|
||||
code: "INVALID_FORMAT"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 400);
|
||||
} else {
|
||||
const response: ErrorResponse<string> = {
|
||||
message: "Unknown error.",
|
||||
errors: [(e as Error).message],
|
||||
code: "UNKNOWN_ERROR"
|
||||
};
|
||||
return c.json<ErrorResponse<string>>(response, 500);
|
||||
}
|
||||
}
|
||||
});
|
32
packages/backend/routes/user/session/[id]/GET.ts
Normal file
32
packages/backend/routes/user/session/[id]/GET.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Context } from "hono";
|
||||
import { Bindings, BlankEnv } from "hono/types";
|
||||
import { ErrorResponse } from "src/schema";
|
||||
import { createHandlers } from "src/utils.ts";
|
||||
import { sqlCred } from "@core/db/dbNew";
|
||||
import { DatabaseUserType } from "@core/db/schema";
|
||||
|
||||
export const getUserByLoginSessionHandler = createHandlers(
|
||||
async (c: Context<BlankEnv & { Bindings: Bindings }, "/user/session/:id">) => {
|
||||
const id = c.req.param("id");
|
||||
const users = await sqlCred<DatabaseUserType[]>`
|
||||
SELECT u.*
|
||||
FROM users u
|
||||
JOIN login_sessions ls ON u.id = ls.uid
|
||||
WHERE ls.id = ${id};
|
||||
`;
|
||||
if (users.length === 0) {
|
||||
const response: ErrorResponse = {
|
||||
message: "Cannot find user",
|
||||
code: "ENTITY_NOT_FOUND",
|
||||
errors: []
|
||||
};
|
||||
return c.json<ErrorResponse>(response, 404);
|
||||
}
|
||||
const user = users[0];
|
||||
return c.json({
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
role: user.role
|
||||
});
|
||||
}
|
||||
);
|
@ -1,23 +1,23 @@
|
||||
import { rootHandler } from "routes";
|
||||
import { pingHandler } from "routes/ping";
|
||||
import { registerHandler } from "routes/user";
|
||||
import { getUserByLoginSessionHandler, registerHandler } from "routes/user";
|
||||
import { videoInfoHandler, getSnapshotsHanlder } from "routes/video";
|
||||
import { Hono } from "hono";
|
||||
import { Variables } from "hono/types";
|
||||
import { createCaptchaSessionHandler, verifyChallengeHandler } from "routes/captcha";
|
||||
import { getCaptchaDifficultyHandler } from "../routes/captcha/difficulty/GET.ts";
|
||||
import { getCaptchaDifficultyHandler } from "routes/captcha/difficulty/GET.ts";
|
||||
|
||||
export function configureRoutes(app: Hono<{ Variables: Variables }>) {
|
||||
app.get("/", ...rootHandler);
|
||||
app.all("/ping", ...pingHandler);
|
||||
|
||||
app.get("/video/:id/snapshots", ...getSnapshotsHanlder);
|
||||
app.post("/user", ...registerHandler);
|
||||
|
||||
app.get("/video/:id/info", ...videoInfoHandler);
|
||||
|
||||
app.post("/user", ...registerHandler);
|
||||
app.get("/user/session/:id", ...getUserByLoginSessionHandler);
|
||||
|
||||
app.post("/captcha/session", ...createCaptchaSessionHandler);
|
||||
app.get("/captcha/:id/result", ...verifyChallengeHandler);
|
||||
|
||||
app.get("/captcha/difficulty", ...getCaptchaDifficultyHandler)
|
||||
app.get("/captcha/difficulty", ...getCaptchaDifficultyHandler);
|
||||
}
|
||||
|
44
packages/backend/src/schema.d.ts
vendored
44
packages/backend/src/schema.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
type ErrorCode =
|
||||
export type ErrorCode =
|
||||
| "INVALID_QUERY_PARAMS"
|
||||
| "UNKNOWN_ERROR"
|
||||
| "INVALID_PAYLOAD"
|
||||
@ -9,14 +9,50 @@ type ErrorCode =
|
||||
| "INVALID_CREDENTIALS"
|
||||
| "ENTITY_NOT_FOUND"
|
||||
| "SERVER_ERROR"
|
||||
| "RATE_LIMIT_EXCEEDED";
|
||||
| "RATE_LIMIT_EXCEEDED"
|
||||
| "ENTITY_EXISTS";
|
||||
|
||||
export interface ErrorResponse<E=string> {
|
||||
export interface ErrorResponse<E = string> {
|
||||
code: ErrorCode;
|
||||
message: string;
|
||||
errors?: E[];
|
||||
errors: E[] = [];
|
||||
i18n?: {
|
||||
key: string;
|
||||
values?: {
|
||||
[key: string]: string | number | Date;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface StatusResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type CaptchaSessionResponse = ErrorResponse | CaptchaSessionRawResponse;
|
||||
|
||||
interface CaptchaSessionRawResponse {
|
||||
success: boolean;
|
||||
id: string;
|
||||
g: string;
|
||||
n: string;
|
||||
t: number;
|
||||
}
|
||||
|
||||
export interface SignUpResponse {
|
||||
username: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface UserResponse {
|
||||
username: string;
|
||||
nickname: string | null;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export type CaptchaVerificationRawResponse = {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export type CaptchaVerificationResponse =
|
||||
| ErrorResponse
|
||||
| CaptchaVerificationRawResponse;
|
||||
|
@ -1,8 +1,8 @@
|
||||
const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD", "DB_PORT", "DB_NAME_CRED"];
|
||||
|
||||
const getEnvVar = (key: string) => {
|
||||
return process.env[key] || import.meta.env[key];
|
||||
}
|
||||
return process.env[key] || import.meta.env[key];
|
||||
};
|
||||
|
||||
const unsetVars = requiredEnvVars.filter((key) => getEnvVar(key) === undefined);
|
||||
|
||||
@ -10,6 +10,8 @@ if (unsetVars.length > 0) {
|
||||
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
|
||||
}
|
||||
|
||||
console.log(process.env);
|
||||
|
||||
const databaseHost = getEnvVar("DB_HOST")!;
|
||||
const databaseName = getEnvVar("DB_NAME");
|
||||
const databaseNameCred = getEnvVar("DB_NAME_CRED")!;
|
||||
|
9
packages/core/db/schema.d.ts
vendored
9
packages/core/db/schema.d.ts
vendored
@ -53,3 +53,12 @@ export interface SnapshotScheduleType {
|
||||
finished_at?: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface DatabaseUserType {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string | null;
|
||||
password: string;
|
||||
unq_id: string;
|
||||
role: string;
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
import type { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||
import type { VideoSnapshotType } from "./schema.d.ts";
|
||||
|
||||
export async function getVideoSnapshots(
|
||||
client: Client,
|
||||
aid: number,
|
||||
limit: number,
|
||||
pageOrOffset: number,
|
||||
reverse: boolean,
|
||||
mode: "page" | "offset" = "page",
|
||||
) {
|
||||
const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset;
|
||||
const queryDesc: string = `
|
||||
SELECT *
|
||||
FROM video_snapshot
|
||||
WHERE aid = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2
|
||||
OFFSET $3
|
||||
`;
|
||||
const queryAsc: string = `
|
||||
SELECT *
|
||||
FROM video_snapshot
|
||||
WHERE aid = $1
|
||||
ORDER BY created_at
|
||||
LIMIT $2 OFFSET $3
|
||||
`;
|
||||
const query = reverse ? queryAsc : queryDesc;
|
||||
const queryResult = await client.queryObject<VideoSnapshotType>(query, [aid, limit, offset]);
|
||||
return queryResult.rows;
|
||||
}
|
||||
|
||||
export async function getVideoSnapshotsByBV(
|
||||
client: Client,
|
||||
bv: string,
|
||||
limit: number,
|
||||
pageOrOffset: number,
|
||||
reverse: boolean,
|
||||
mode: "page" | "offset" = "page",
|
||||
) {
|
||||
const offset = mode === "page" ? (pageOrOffset - 1) * limit : pageOrOffset;
|
||||
const queryAsc = `
|
||||
SELECT vs.*
|
||||
FROM video_snapshot vs
|
||||
JOIN bilibili_metadata bm ON vs.aid = bm.aid
|
||||
WHERE bm.bvid = $1
|
||||
ORDER BY vs.created_at
|
||||
LIMIT $2
|
||||
OFFSET $3
|
||||
`;
|
||||
const queryDesc: string = `
|
||||
SELECT *
|
||||
FROM video_snapshot vs
|
||||
JOIN bilibili_metadata bm ON vs.aid = bm.aid
|
||||
WHERE bm.bvid = $1
|
||||
ORDER BY vs.created_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`;
|
||||
const query = reverse ? queryAsc : queryDesc;
|
||||
const queryResult = await client.queryObject<VideoSnapshotType>(query, [bv, limit, offset]);
|
||||
return queryResult.rows;
|
||||
}
|
1
packages/core/index.ts
Normal file
1
packages/core/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./db/dbNew";
|
@ -1,7 +1,10 @@
|
||||
{
|
||||
"name": "core",
|
||||
"name": "@cvsa/core",
|
||||
"private": false,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"test": "bun --env-file=.env.test run vitest"
|
||||
"test": "bun --env-file=.env.test run vitest",
|
||||
"build": "bun build ./index.ts --target node --outdir ./dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@koshnic/ratelimit": "^1.0.3",
|
||||
@ -13,5 +16,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./types.d.ts"
|
||||
}
|
||||
|
1
packages/core/types.d.ts
vendored
Normal file
1
packages/core/types.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from "./db/schema";
|
449
packages/next/app/[locale]/fonts/InterFont/Inter.css
Normal file
449
packages/next/app/[locale]/fonts/InterFont/Inter.css
Normal file
@ -0,0 +1,449 @@
|
||||
@font-face {
|
||||
font-family: "Inter Variable";
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url("InterVariable.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter Variable";
|
||||
font-style: italic;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url("InterVariable-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
/* static fonts */
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ThinItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-LightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-MediumItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-SemiBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Black.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-BlackItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-feature-values InterVariable {
|
||||
@character-variant {
|
||||
cv01: 1;
|
||||
cv02: 2;
|
||||
cv03: 3;
|
||||
cv04: 4;
|
||||
cv05: 5;
|
||||
cv06: 6;
|
||||
cv07: 7;
|
||||
cv08: 8;
|
||||
cv09: 9;
|
||||
cv10: 10;
|
||||
cv11: 11;
|
||||
cv12: 12;
|
||||
cv13: 13;
|
||||
alt-1: 1; /* Alternate one */
|
||||
alt-3: 9; /* Flat-top three */
|
||||
open-4: 2; /* Open four */
|
||||
open-6: 3; /* Open six */
|
||||
open-9: 4; /* Open nine */
|
||||
lc-l-with-tail: 5; /* Lower-case L with tail */
|
||||
simplified-u: 6; /* Simplified u */
|
||||
alt-double-s: 7; /* Alternate German double s */
|
||||
uc-i-with-serif: 8; /* Upper-case i with serif */
|
||||
uc-g-with-spur: 10; /* Capital G with spur */
|
||||
single-story-a: 11; /* Single-story a */
|
||||
compact-lc-f: 12; /* Compact f */
|
||||
compact-lc-t: 13; /* Compact t */
|
||||
}
|
||||
@styleset {
|
||||
ss01: 1;
|
||||
ss02: 2;
|
||||
ss03: 3;
|
||||
ss04: 4;
|
||||
ss05: 5;
|
||||
ss06: 6;
|
||||
ss07: 7;
|
||||
ss08: 8;
|
||||
open-digits: 1; /* Open digits */
|
||||
disambiguation: 2; /* Disambiguation (with zero) */
|
||||
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
|
||||
round-quotes-and-commas: 3; /* Round quotes & commas */
|
||||
square-punctuation: 7; /* Square punctuation */
|
||||
square-quotes: 8; /* Square quotes */
|
||||
circled-characters: 5; /* Circled characters */
|
||||
squared-characters: 6; /* Squared characters */
|
||||
}
|
||||
}
|
||||
@font-feature-values Inter {
|
||||
@character-variant {
|
||||
cv01: 1;
|
||||
cv02: 2;
|
||||
cv03: 3;
|
||||
cv04: 4;
|
||||
cv05: 5;
|
||||
cv06: 6;
|
||||
cv07: 7;
|
||||
cv08: 8;
|
||||
cv09: 9;
|
||||
cv10: 10;
|
||||
cv11: 11;
|
||||
cv12: 12;
|
||||
cv13: 13;
|
||||
alt-1: 1; /* Alternate one */
|
||||
alt-3: 9; /* Flat-top three */
|
||||
open-4: 2; /* Open four */
|
||||
open-6: 3; /* Open six */
|
||||
open-9: 4; /* Open nine */
|
||||
lc-l-with-tail: 5; /* Lower-case L with tail */
|
||||
simplified-u: 6; /* Simplified u */
|
||||
alt-double-s: 7; /* Alternate German double s */
|
||||
uc-i-with-serif: 8; /* Upper-case i with serif */
|
||||
uc-g-with-spur: 10; /* Capital G with spur */
|
||||
single-story-a: 11; /* Single-story a */
|
||||
compact-lc-f: 12; /* Compact f */
|
||||
compact-lc-t: 13; /* Compact t */
|
||||
}
|
||||
@styleset {
|
||||
ss01: 1;
|
||||
ss02: 2;
|
||||
ss03: 3;
|
||||
ss04: 4;
|
||||
ss05: 5;
|
||||
ss06: 6;
|
||||
ss07: 7;
|
||||
ss08: 8;
|
||||
open-digits: 1; /* Open digits */
|
||||
disambiguation: 2; /* Disambiguation (with zero) */
|
||||
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
|
||||
round-quotes-and-commas: 3; /* Round quotes & commas */
|
||||
square-punctuation: 7; /* Square punctuation */
|
||||
square-quotes: 8; /* Square quotes */
|
||||
circled-characters: 5; /* Circled characters */
|
||||
squared-characters: 6; /* Squared characters */
|
||||
}
|
||||
}
|
||||
@font-feature-values InterDisplay {
|
||||
@character-variant {
|
||||
cv01: 1;
|
||||
cv02: 2;
|
||||
cv03: 3;
|
||||
cv04: 4;
|
||||
cv05: 5;
|
||||
cv06: 6;
|
||||
cv07: 7;
|
||||
cv08: 8;
|
||||
cv09: 9;
|
||||
cv10: 10;
|
||||
cv11: 11;
|
||||
cv12: 12;
|
||||
cv13: 13;
|
||||
alt-1: 1; /* Alternate one */
|
||||
alt-3: 9; /* Flat-top three */
|
||||
open-4: 2; /* Open four */
|
||||
open-6: 3; /* Open six */
|
||||
open-9: 4; /* Open nine */
|
||||
lc-l-with-tail: 5; /* Lower-case L with tail */
|
||||
simplified-u: 6; /* Simplified u */
|
||||
alt-double-s: 7; /* Alternate German double s */
|
||||
uc-i-with-serif: 8; /* Upper-case i with serif */
|
||||
uc-g-with-spur: 10; /* Capital G with spur */
|
||||
single-story-a: 11; /* Single-story a */
|
||||
compact-lc-f: 12; /* Compact f */
|
||||
compact-lc-t: 13; /* Compact t */
|
||||
}
|
||||
@styleset {
|
||||
ss01: 1;
|
||||
ss02: 2;
|
||||
ss03: 3;
|
||||
ss04: 4;
|
||||
ss05: 5;
|
||||
ss06: 6;
|
||||
ss07: 7;
|
||||
ss08: 8;
|
||||
open-digits: 1; /* Open digits */
|
||||
disambiguation: 2; /* Disambiguation (with zero) */
|
||||
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
|
||||
round-quotes-and-commas: 3; /* Round quotes & commas */
|
||||
square-punctuation: 7; /* Square punctuation */
|
||||
square-quotes: 8; /* Square quotes */
|
||||
circled-characters: 5; /* Circled characters */
|
||||
squared-characters: 6; /* Squared characters */
|
||||
}
|
||||
}
|
87
packages/next/app/[locale]/fonts/MiSans/MiSans.css
Normal file
87
packages/next/app/[locale]/fonts/MiSans/MiSans.css
Normal file
@ -0,0 +1,87 @@
|
||||
@font-face {
|
||||
font-family: "MiSans VF";
|
||||
font-style: normal;
|
||||
font-weight: 150 700;
|
||||
font-display: optional;
|
||||
src: url("MiSans VF.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("MiSans-ExtraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 360;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Demibold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Semibold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Heavy.woff2") format("woff2");
|
||||
}
|
@ -79,8 +79,6 @@
|
||||
--color-dark-on-error: #690005;
|
||||
--color-dark-error-container: #93000a;
|
||||
--color-dark-on-error-container: #ffdad6;
|
||||
|
||||
--font-zh: "InterVariable", "MiSans VF", sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
@ -88,7 +86,7 @@ a {
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: "MiSans", "Inter", sans-serif;
|
||||
font-family: "Inter", "MiSans", sans-serif;
|
||||
font-weight: 400;
|
||||
@apply bg-surface dark:bg-dark-surface text-on-surface dark:text-dark-on-surface;
|
||||
}
|
||||
@ -101,7 +99,7 @@ a {
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
:root {
|
||||
font-family: "InterVariable", "MiSans VF", sans-serif;
|
||||
font-family: "Inter Variable", "MiSans VF", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 330;
|
||||
}
|
@ -1,16 +1,25 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import React from "react";
|
||||
import { routing } from "@/i18n/routing";
|
||||
import { NextIntlClientProvider, hasLocale } from "next-intl";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "中V档案馆"
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ locale: string }>;
|
||||
}>) {
|
||||
const { locale } = await params;
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound();
|
||||
}
|
||||
return (
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
@ -19,8 +28,10 @@ export default function RootLayout({
|
||||
<title>中V档案馆</title>
|
||||
</head>
|
||||
<body className="min-h-screen flex flex-col">
|
||||
{children}
|
||||
<div id="portal-root"></div>
|
||||
<NextIntlClientProvider>
|
||||
{children}
|
||||
<div id="portal-root"></div>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
22
packages/next/app/[locale]/page.tsx
Normal file
22
packages/next/app/[locale]/page.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Header } from "@/components/shell/Header";
|
||||
import { getUserBySession } from "@/lib/db/user";
|
||||
import { UserResponse } from "@backend/src/schema";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export default async function Home() {
|
||||
const cookieStore = await cookies();
|
||||
const sessionID = cookieStore.get("session_id");
|
||||
let user: undefined | UserResponse = undefined;
|
||||
if (sessionID) {
|
||||
user = await getUserBySession(sessionID.value);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Header user={user} />
|
||||
<main className="flex flex-col items-center justify-center h-full flex-grow gap-8 px-4">
|
||||
<h1 className="text-4xl font-medium text-center">正在施工中……</h1>
|
||||
<p>在搜索栏输入BV号或AV号,可以查询目前数据库收集到的信息~</p>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
22
packages/next/app/[locale]/signup/ErrorDialog.tsx
Normal file
22
packages/next/app/[locale]/signup/ErrorDialog.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { DialogButton, DialogButtonGroup, DialogHeadline, DialogSupportingText } from "@/components/ui/Dialog";
|
||||
import { ErrorCode as ResponseErrorCode } from "@backend/src/schema";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ErrorDialogProps {
|
||||
children: React.ReactNode;
|
||||
closeDialog: () => void;
|
||||
errorCode?: ResponseErrorCode;
|
||||
}
|
||||
|
||||
export const ErrorDialog: React.FC<ErrorDialogProps> = ({ children, closeDialog, errorCode }) => {
|
||||
const t = useTranslations("backend.error_code");
|
||||
return (
|
||||
<>
|
||||
<DialogHeadline>{errorCode ? t(errorCode) : "错误"}</DialogHeadline>
|
||||
<DialogSupportingText>{children}</DialogSupportingText>
|
||||
<DialogButtonGroup>
|
||||
<DialogButton onClick={closeDialog}>关闭</DialogButton>
|
||||
</DialogButtonGroup>
|
||||
</>
|
||||
);
|
||||
};
|
146
packages/next/app/[locale]/signup/SignUpForm.tsx
Normal file
146
packages/next/app/[locale]/signup/SignUpForm.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import TextField from "@/components/ui/TextField";
|
||||
import LoadingSpinner from "@/components/icons/LoadingSpinner";
|
||||
import { Portal } from "@/components/utils/Portal";
|
||||
import { Dialog } from "@/components/ui/Dialog";
|
||||
import { setLocale } from "yup";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCaptcha } from "@/components/hooks/useCaptcha";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { requestSignUp } from "./request";
|
||||
import { FilledButton } from "@/components/ui/Buttons/FilledButton";
|
||||
import { ErrorDialog } from "./ErrorDialog";
|
||||
import { ApiRequestError } from "@/lib/net";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
setLocale({
|
||||
mixed: {
|
||||
default: "field_invalid",
|
||||
required: () => ({ key: "field_required" })
|
||||
},
|
||||
string: {
|
||||
min: ({ min }) => ({ key: "field_too_short", values: { min } }),
|
||||
max: ({ max }) => ({ key: "field_too_big", values: { max } })
|
||||
}
|
||||
});
|
||||
|
||||
export interface LocalizedMessage {
|
||||
key: string;
|
||||
values: {
|
||||
[key: string]: number | string;
|
||||
};
|
||||
}
|
||||
|
||||
interface RegistrationFormProps {
|
||||
backendURL: string;
|
||||
}
|
||||
|
||||
const SignUpForm: React.FC<RegistrationFormProps> = ({ backendURL }) => {
|
||||
const [usernameInput, setUsername] = useState("");
|
||||
const [passwordInput, setPassword] = useState("");
|
||||
const [nicknameInput, setNickname] = useState("");
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const [dialogContent, setDialogContent] = useState(<></>);
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const t = useTranslations("");
|
||||
const { startCaptcha, captchaResult, captchaUsed, setCaptchaUsedState, captchaError } = useCaptcha({
|
||||
backendURL,
|
||||
route: "POST-/user"
|
||||
});
|
||||
const { trigger } = useSWRMutation(`${backendURL}/user`, requestSignUp);
|
||||
const router = useRouter();
|
||||
|
||||
const translateErrorMessage = (item: LocalizedMessage | string, path?: string) => {
|
||||
if (typeof item === "string") {
|
||||
return item;
|
||||
}
|
||||
return t(`yup_errors.${item.key}`, { ...item.values, field: path ? t(path) : "" });
|
||||
};
|
||||
|
||||
const register = async () => {
|
||||
try {
|
||||
if (captchaUsed || !captchaResult) {
|
||||
await startCaptcha();
|
||||
}
|
||||
|
||||
await trigger({
|
||||
data: {
|
||||
username: usernameInput,
|
||||
password: passwordInput,
|
||||
nickname: nicknameInput
|
||||
},
|
||||
setShowDialog,
|
||||
captchaResult,
|
||||
setCaptchaUsedState,
|
||||
translateErrorMessage,
|
||||
setDialogContent,
|
||||
t
|
||||
});
|
||||
|
||||
router.push("/");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!captchaError || captchaError === undefined) return;
|
||||
const err = captchaError as ApiRequestError;
|
||||
setShowDialog(true);
|
||||
if (err.code && err.code == -1) {
|
||||
setDialogContent(
|
||||
<ErrorDialog closeDialog={() => setShowDialog(false)}>
|
||||
<p>无法连接到服务器,请检查你的网络连接后重试。</p>
|
||||
</ErrorDialog>
|
||||
);
|
||||
}
|
||||
}, [captchaError]);
|
||||
|
||||
useEffect(() => {
|
||||
startCaptcha();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form
|
||||
className="w-full flex flex-col gap-6"
|
||||
onSubmit={async (e) => {
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
await register();
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
labelText="用户名"
|
||||
inputText={usernameInput}
|
||||
onInputTextChange={setUsername}
|
||||
maxChar={50}
|
||||
supportingText="*必填。用户名是唯一的,不区分大小写。"
|
||||
/>
|
||||
<TextField
|
||||
labelText="密码"
|
||||
type="password"
|
||||
inputText={passwordInput}
|
||||
onInputTextChange={setPassword}
|
||||
supportingText="*必填。密码至少为 4 个字符。"
|
||||
maxChar={120}
|
||||
/>
|
||||
<TextField
|
||||
labelText="昵称"
|
||||
inputText={nicknameInput}
|
||||
onInputTextChange={setNickname}
|
||||
supportingText="昵称可以重复。"
|
||||
maxChar={30}
|
||||
/>
|
||||
<FilledButton type="submit" disabled={isLoading}>
|
||||
{isLoading ? <LoadingSpinner /> : <span>注册</span>}
|
||||
</FilledButton>
|
||||
<Portal>
|
||||
<Dialog show={showDialog}>{dialogContent}</Dialog>
|
||||
</Portal>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpForm;
|
@ -4,10 +4,10 @@ import SignUpForm from "./SignUpForm";
|
||||
|
||||
export default function SignupPage() {
|
||||
return (
|
||||
<main className="relative flex-grow pt-8 md:pt-0 px-4 md:w-full md:h-full md:flex md:items-center md:justify-center">
|
||||
<main className="relative flex-grow pt-8 px-4 md:w-full md:h-full md:flex md:items-center md:justify-center">
|
||||
<div
|
||||
className="md:w-[40rem] rounded-md md:p-8 md:-translate-y-6
|
||||
md:bg-surface-container md:dark:bg-dark-surface-container"
|
||||
md:bg-surface-container md:dark:bg-dark-surface-container"
|
||||
>
|
||||
<p className="mb-2">
|
||||
<a href="/">
|
151
packages/next/app/[locale]/signup/request.tsx
Normal file
151
packages/next/app/[locale]/signup/request.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import { Dispatch, JSX, SetStateAction } from "react";
|
||||
import { ApiRequestError, fetcher } from "@/lib/net";
|
||||
import type { CaptchaVerificationRawResponse, ErrorResponse, SignUpResponse } from "@backend/src/schema";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { LocalizedMessage } from "./SignUpForm";
|
||||
import { ErrorDialog } from "./ErrorDialog";
|
||||
import { string, object, ValidationError } from "yup";
|
||||
|
||||
interface SignUpFormData {
|
||||
username: string;
|
||||
password: string;
|
||||
nickname?: string;
|
||||
}
|
||||
|
||||
const FormSchema = object().shape({
|
||||
username: string().required().max(50),
|
||||
password: string().required().min(4).max(120),
|
||||
nickname: string().optional().max(30)
|
||||
});
|
||||
|
||||
const validateForm = async (
|
||||
data: SignUpFormData,
|
||||
setShowDialog: Dispatch<SetStateAction<boolean>>,
|
||||
setDialogContent: Dispatch<SetStateAction<JSX.Element>>,
|
||||
translateErrorMessage: (item: LocalizedMessage | string, path?: string) => string
|
||||
): Promise<SignUpFormData | null> => {
|
||||
const { username: usernameInput, password: passwordInput, nickname: nicknameInput } = data;
|
||||
try {
|
||||
const formData = await FormSchema.validate(
|
||||
{
|
||||
username: usernameInput,
|
||||
password: passwordInput,
|
||||
nickname: nicknameInput
|
||||
},
|
||||
{ abortEarly: false }
|
||||
);
|
||||
return {
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
nickname: formData.nickname
|
||||
};
|
||||
} catch (e) {
|
||||
if (!(e instanceof ValidationError)) {
|
||||
return null;
|
||||
}
|
||||
setShowDialog(true);
|
||||
setDialogContent(
|
||||
<ErrorDialog closeDialog={() => setShowDialog(false)}>
|
||||
<p>注册信息填写有误,请检查后重新提交。</p>
|
||||
<span>错误信息: </span>
|
||||
<br />
|
||||
<ol className="list-decimal list-inside">
|
||||
{e.errors.map((item, i) => {
|
||||
return <li key={i}>{translateErrorMessage(item, e.inner[i].path)}</li>;
|
||||
})}
|
||||
</ol>
|
||||
</ErrorDialog>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
interface RequestSignUpArgs {
|
||||
data: SignUpFormData;
|
||||
setShowDialog: Dispatch<SetStateAction<boolean>>;
|
||||
setDialogContent: Dispatch<SetStateAction<JSX.Element>>;
|
||||
translateErrorMessage: (item: LocalizedMessage | string, path?: string) => string;
|
||||
setCaptchaUsedState: Dispatch<SetStateAction<boolean>>;
|
||||
captchaResult: CaptchaVerificationRawResponse | undefined;
|
||||
t: any;
|
||||
}
|
||||
|
||||
export const requestSignUp = async (url: string, { arg }: { arg: RequestSignUpArgs }) => {
|
||||
const { data, setShowDialog, setDialogContent, translateErrorMessage, setCaptchaUsedState, captchaResult, t } = arg;
|
||||
const res = await validateForm(data, setShowDialog, setDialogContent, translateErrorMessage);
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
const { username, nickname, password } = res;
|
||||
|
||||
try {
|
||||
if (!captchaResult) {
|
||||
const err = new ApiRequestError("Cannot get captcha result");
|
||||
err.response = {
|
||||
code: "UNKNOWN_ERROR",
|
||||
message: "Cannot get captch verifiction result",
|
||||
i18n: {
|
||||
key: "captcha_failed_to_get"
|
||||
}
|
||||
} as ErrorResponse;
|
||||
throw err;
|
||||
}
|
||||
setCaptchaUsedState(true);
|
||||
const registrationResponse = await fetcher<SignUpResponse>(url, {
|
||||
method: "POST",
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${captchaResult!.token}`
|
||||
},
|
||||
data: {
|
||||
username: username,
|
||||
password: password,
|
||||
nickname: nickname
|
||||
}
|
||||
});
|
||||
return registrationResponse;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiRequestError && error.response) {
|
||||
const res = error.response as ErrorResponse;
|
||||
setShowDialog(true);
|
||||
setDialogContent(
|
||||
<ErrorDialog closeDialog={() => setShowDialog(false)} errorCode={res.code}>
|
||||
<p>无法为你注册账户。</p>
|
||||
<p>
|
||||
错误信息: <br />
|
||||
{res.i18n
|
||||
? t.rich(res.i18n.key, {
|
||||
...res.i18n.values,
|
||||
support: (chunks: string) => <Link href="/support">{chunks}</Link>
|
||||
})
|
||||
: res.message}
|
||||
</p>
|
||||
</ErrorDialog>
|
||||
);
|
||||
} else if (error instanceof Error) {
|
||||
setShowDialog(true);
|
||||
setDialogContent(
|
||||
<ErrorDialog closeDialog={() => setShowDialog(false)}>
|
||||
<p>无法为你注册账户。</p>
|
||||
<p>
|
||||
错误信息:
|
||||
<br />
|
||||
{error.message}
|
||||
</p>
|
||||
</ErrorDialog>
|
||||
);
|
||||
} else {
|
||||
setShowDialog(true);
|
||||
setDialogContent(
|
||||
<ErrorDialog closeDialog={() => setShowDialog(false)} errorCode="UNKNOWN_ERROR">
|
||||
<p>无法为你注册账户。</p>
|
||||
<p>
|
||||
错误信息: <br />
|
||||
<pre className="break-all">{JSON.stringify(error)}</pre>
|
||||
</p>
|
||||
</ErrorDialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,450 +0,0 @@
|
||||
@font-face {
|
||||
font-family: InterVariable;
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url("InterVariable.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: InterVariable;
|
||||
font-style: italic;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url("InterVariable-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
/* static fonts */
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ThinItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-LightItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Italic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-MediumItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-SemiBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-BoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraBold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-Black.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "InterDisplay";
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("InterDisplay-BlackItalic.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-feature-values InterVariable {
|
||||
@character-variant {
|
||||
cv01: 1;
|
||||
cv02: 2;
|
||||
cv03: 3;
|
||||
cv04: 4;
|
||||
cv05: 5;
|
||||
cv06: 6;
|
||||
cv07: 7;
|
||||
cv08: 8;
|
||||
cv09: 9;
|
||||
cv10: 10;
|
||||
cv11: 11;
|
||||
cv12: 12;
|
||||
cv13: 13;
|
||||
alt-1: 1; /* Alternate one */
|
||||
alt-3: 9; /* Flat-top three */
|
||||
open-4: 2; /* Open four */
|
||||
open-6: 3; /* Open six */
|
||||
open-9: 4; /* Open nine */
|
||||
lc-l-with-tail: 5; /* Lower-case L with tail */
|
||||
simplified-u: 6; /* Simplified u */
|
||||
alt-double-s: 7; /* Alternate German double s */
|
||||
uc-i-with-serif: 8; /* Upper-case i with serif */
|
||||
uc-g-with-spur: 10; /* Capital G with spur */
|
||||
single-story-a: 11; /* Single-story a */
|
||||
compact-lc-f: 12; /* Compact f */
|
||||
compact-lc-t: 13; /* Compact t */
|
||||
}
|
||||
@styleset {
|
||||
ss01: 1;
|
||||
ss02: 2;
|
||||
ss03: 3;
|
||||
ss04: 4;
|
||||
ss05: 5;
|
||||
ss06: 6;
|
||||
ss07: 7;
|
||||
ss08: 8;
|
||||
open-digits: 1; /* Open digits */
|
||||
disambiguation: 2; /* Disambiguation (with zero) */
|
||||
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
|
||||
round-quotes-and-commas: 3; /* Round quotes & commas */
|
||||
square-punctuation: 7; /* Square punctuation */
|
||||
square-quotes: 8; /* Square quotes */
|
||||
circled-characters: 5; /* Circled characters */
|
||||
squared-characters: 6; /* Squared characters */
|
||||
}
|
||||
}
|
||||
@font-feature-values Inter {
|
||||
|
||||
@character-variant {
|
||||
cv01: 1;
|
||||
cv02: 2;
|
||||
cv03: 3;
|
||||
cv04: 4;
|
||||
cv05: 5;
|
||||
cv06: 6;
|
||||
cv07: 7;
|
||||
cv08: 8;
|
||||
cv09: 9;
|
||||
cv10: 10;
|
||||
cv11: 11;
|
||||
cv12: 12;
|
||||
cv13: 13;
|
||||
alt-1: 1; /* Alternate one */
|
||||
alt-3: 9; /* Flat-top three */
|
||||
open-4: 2; /* Open four */
|
||||
open-6: 3; /* Open six */
|
||||
open-9: 4; /* Open nine */
|
||||
lc-l-with-tail: 5; /* Lower-case L with tail */
|
||||
simplified-u: 6; /* Simplified u */
|
||||
alt-double-s: 7; /* Alternate German double s */
|
||||
uc-i-with-serif: 8; /* Upper-case i with serif */
|
||||
uc-g-with-spur: 10; /* Capital G with spur */
|
||||
single-story-a: 11; /* Single-story a */
|
||||
compact-lc-f: 12; /* Compact f */
|
||||
compact-lc-t: 13; /* Compact t */
|
||||
}
|
||||
@styleset {
|
||||
ss01: 1;
|
||||
ss02: 2;
|
||||
ss03: 3;
|
||||
ss04: 4;
|
||||
ss05: 5;
|
||||
ss06: 6;
|
||||
ss07: 7;
|
||||
ss08: 8;
|
||||
open-digits: 1; /* Open digits */
|
||||
disambiguation: 2; /* Disambiguation (with zero) */
|
||||
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
|
||||
round-quotes-and-commas: 3; /* Round quotes & commas */
|
||||
square-punctuation: 7; /* Square punctuation */
|
||||
square-quotes: 8; /* Square quotes */
|
||||
circled-characters: 5; /* Circled characters */
|
||||
squared-characters: 6; /* Squared characters */
|
||||
}
|
||||
}
|
||||
@font-feature-values InterDisplay {
|
||||
@character-variant {
|
||||
cv01: 1;
|
||||
cv02: 2;
|
||||
cv03: 3;
|
||||
cv04: 4;
|
||||
cv05: 5;
|
||||
cv06: 6;
|
||||
cv07: 7;
|
||||
cv08: 8;
|
||||
cv09: 9;
|
||||
cv10: 10;
|
||||
cv11: 11;
|
||||
cv12: 12;
|
||||
cv13: 13;
|
||||
alt-1: 1; /* Alternate one */
|
||||
alt-3: 9; /* Flat-top three */
|
||||
open-4: 2; /* Open four */
|
||||
open-6: 3; /* Open six */
|
||||
open-9: 4; /* Open nine */
|
||||
lc-l-with-tail: 5; /* Lower-case L with tail */
|
||||
simplified-u: 6; /* Simplified u */
|
||||
alt-double-s: 7; /* Alternate German double s */
|
||||
uc-i-with-serif: 8; /* Upper-case i with serif */
|
||||
uc-g-with-spur: 10; /* Capital G with spur */
|
||||
single-story-a: 11; /* Single-story a */
|
||||
compact-lc-f: 12; /* Compact f */
|
||||
compact-lc-t: 13; /* Compact t */
|
||||
}
|
||||
@styleset {
|
||||
ss01: 1;
|
||||
ss02: 2;
|
||||
ss03: 3;
|
||||
ss04: 4;
|
||||
ss05: 5;
|
||||
ss06: 6;
|
||||
ss07: 7;
|
||||
ss08: 8;
|
||||
open-digits: 1; /* Open digits */
|
||||
disambiguation: 2; /* Disambiguation (with zero) */
|
||||
disambiguation-except-zero: 4; /* Disambiguation (no zero) */
|
||||
round-quotes-and-commas: 3; /* Round quotes & commas */
|
||||
square-punctuation: 7; /* Square punctuation */
|
||||
square-quotes: 8; /* Square quotes */
|
||||
circled-characters: 5; /* Circled characters */
|
||||
squared-characters: 6; /* Squared characters */
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "MiSans VF";
|
||||
font-style: normal;
|
||||
font-weight: 150 700;
|
||||
font-display: swap;
|
||||
src: url("MiSans VF.woff2") format("woff2");
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Thin.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("MiSans-ExtraLight.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Light.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 360;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Normal.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Demibold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Semibold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "MiSans";
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("MiSans-Heavy.woff2") format("woff2");
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { Header } from "@/components/shell/Header";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="flex flex-col items-center justify-center h-full flex-grow gap-8 px-4">
|
||||
<h1 className="text-4xl font-medium text-center">正在施工中……</h1>
|
||||
<p>在搜索栏输入BV号或AV号,可以查询目前数据库收集到的信息~</p>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import TextField from "@/components/ui/TextField";
|
||||
import LoadingSpinner from "@/components/icons/LoadingSpinner";
|
||||
import { computeVdfInWorker } from "@/lib/vdf";
|
||||
import useSWR from "swr";
|
||||
import { ApiRequestError } from "@/lib/net";
|
||||
import { Portal } from "@/components/utils/Portal";
|
||||
import { Dialog, DialogButton, DialogButtonGroup, DialogHeadline, DialogSupportingText } from "@/components/ui/Dialog";
|
||||
import { FilledButton } from "@/components/ui/Buttons/FilledButton";
|
||||
|
||||
interface CaptchaSessionResponse {
|
||||
g: string;
|
||||
n: string;
|
||||
t: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface CaptchaResultResponse {
|
||||
token: string;
|
||||
}
|
||||
|
||||
async function fetcher<JSON = any>(input: RequestInfo, init?: RequestInit): Promise<JSON> {
|
||||
const res = await fetch(input, init);
|
||||
if (!res.ok) {
|
||||
const error = new ApiRequestError("An error occurred while fetching the data.");
|
||||
error.response = await res.json();
|
||||
error.code = res.status;
|
||||
throw error;
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
interface RegistrationFormProps {
|
||||
backendURL: string;
|
||||
}
|
||||
|
||||
const SignUpForm: React.FC<RegistrationFormProps> = ({ backendURL }) => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [nickname, setNickname] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const [dialogContent, setDialogContent] = useState(<></>);
|
||||
|
||||
const {
|
||||
data: captchaSession,
|
||||
error: captchaSessionError,
|
||||
mutate: createCaptchaSession
|
||||
} = useSWR<CaptchaSessionResponse>(
|
||||
`${backendURL}/captcha/session`,
|
||||
(url) =>
|
||||
fetcher(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ route: "POST-/user" })
|
||||
}),
|
||||
{ revalidateOnFocus: false, revalidateOnReconnect: false }
|
||||
);
|
||||
|
||||
const getCaptchaResult = async (id: string, ans: string): Promise<CaptchaResultResponse> => {
|
||||
const url = new URL(`${backendURL}/captcha/${id}/result`);
|
||||
url.searchParams.set("ans", ans);
|
||||
return fetcher<CaptchaResultResponse>(url.toString());
|
||||
};
|
||||
|
||||
const register = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
if (!captchaSession?.g || !captchaSession?.n || !captchaSession?.t || !captchaSession?.id) {
|
||||
console.error("Captcha session data is missing.");
|
||||
return;
|
||||
}
|
||||
const ans = await computeVdfInWorker(
|
||||
BigInt(captchaSession.g),
|
||||
BigInt(captchaSession.n),
|
||||
BigInt(captchaSession.t)
|
||||
);
|
||||
const captchaResult = await getCaptchaResult(captchaSession.id, ans.result.toString());
|
||||
|
||||
if (!captchaResult.token) {
|
||||
}
|
||||
// Proceed with user registration using username, password, and nickname
|
||||
const registrationUrl = new URL(`${backendURL}/user`);
|
||||
const registrationResponse = await fetch(registrationUrl.toString(), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${captchaResult.token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password,
|
||||
nickname
|
||||
})
|
||||
});
|
||||
|
||||
if (registrationResponse.ok) {
|
||||
console.log("Registration successful!");
|
||||
// Optionally redirect the user or show a success message
|
||||
//router.push("/login"); // Example redirection
|
||||
} else {
|
||||
console.error("Registration failed:", await registrationResponse.json());
|
||||
// Handle registration error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Registration process error:", error);
|
||||
// Handle general error
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
className="w-full flex flex-col gap-6"
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
await register();
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
labelText="用户名"
|
||||
inputText={username}
|
||||
onInputTextChange={setUsername}
|
||||
maxChar={50}
|
||||
supportingText="*必填。用户名是唯一的,不区分大小写。"
|
||||
/>
|
||||
<TextField
|
||||
labelText="密码"
|
||||
type="password"
|
||||
inputText={password}
|
||||
onInputTextChange={setPassword}
|
||||
supportingText="*必填。密码至少为 4 个字符。"
|
||||
maxChar={120}
|
||||
/>
|
||||
<TextField
|
||||
labelText="昵称"
|
||||
inputText={nickname}
|
||||
onInputTextChange={setNickname}
|
||||
supportingText="昵称可以重复。"
|
||||
maxChar={30}
|
||||
/>
|
||||
<FilledButton
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowDialog(true);
|
||||
setDialogContent(
|
||||
<>
|
||||
<DialogHeadline>Error</DialogHeadline>
|
||||
<DialogSupportingText>
|
||||
<p>Your operation frequency is too high. Please try again later. (RATE_LIMIT_EXCEED)</p>
|
||||
</DialogSupportingText>
|
||||
<DialogButtonGroup>
|
||||
<DialogButton onClick={() => setShowDialog(false)}>Close</DialogButton>
|
||||
</DialogButtonGroup>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
size="m"
|
||||
shape="square"
|
||||
>
|
||||
Show Dialog
|
||||
</FilledButton>
|
||||
<FilledButton type="submit" disabled={loading}>
|
||||
{!loading ? <span>注册</span> : <LoadingSpinner />}
|
||||
</FilledButton>
|
||||
<Portal>
|
||||
<Dialog show={showDialog}>{dialogContent}</Dialog>
|
||||
</Portal>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpForm;
|
@ -4,19 +4,24 @@
|
||||
"": {
|
||||
"name": "next",
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.12.2",
|
||||
"next": "^15.1.8",
|
||||
"@cvsa/core": "^0.0.1",
|
||||
"axios": "^1.9.0",
|
||||
"framer-motion": "^12.15.0",
|
||||
"i18next": "^25.2.1",
|
||||
"next": "^15.3.3",
|
||||
"next-intl": "^4.1.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"swr": "^2.3.3",
|
||||
"yup": "^1.6.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@types/node": "^20.17.57",
|
||||
"@types/react": "^19.1.6",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"typescript": "^5.8.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -25,8 +30,26 @@
|
||||
|
||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.27.4", "", {}, "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA=="],
|
||||
|
||||
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
|
||||
|
||||
"@cvsa/core": ["@cvsa/core@0.0.1", "", { "dependencies": { "@koshnic/ratelimit": "^1.0.3", "chalk": "^5.4.1", "ioredis": "^5.6.1", "logform": "^2.7.0", "postgres": "^3.4.5", "winston": "^3.17.0" } }, "sha512-h7p2AHcvdIA7GCJq4k1sOSGGbs/qjdHa4WlcCh6p1rVgpkpXp6v1Q9lvXca3uqAkInwzXctDSGwKiQp65K5XOg=="],
|
||||
|
||||
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
|
||||
"@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.4", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.1", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA=="],
|
||||
|
||||
"@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],
|
||||
|
||||
"@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.2", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/icu-skeleton-parser": "1.8.14", "tslib": "^2.8.0" } }, "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA=="],
|
||||
|
||||
"@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.14", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "tslib": "^2.8.0" } }, "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ=="],
|
||||
|
||||
"@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.5.10", "", { "dependencies": { "tslib": "2" } }, "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.1.0" }, "os": "darwin", "cpu": "arm64" }, "sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.1.0" }, "os": "darwin", "cpu": "x64" }, "sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g=="],
|
||||
@ -69,6 +92,8 @@
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw=="],
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="],
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||
@ -81,72 +106,126 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||
|
||||
"@next/env": ["@next/env@15.3.2", "", {}, "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g=="],
|
||||
"@koshnic/ratelimit": ["@koshnic/ratelimit@1.0.3", "", { "dependencies": { "@types/chai": "^4.3.9", "@types/mocha": "^10.0.3", "chai": "^4.3.10", "ioredis": "^5.3.2", "mocha": "^10.2.0" } }, "sha512-cfDcSc+I+M4hNM+/4M+lfn8UuTq4OEFKl78ThOcGNaO7g8tWb1vm2qVpV1p1loYao1mqk00NBNwHQu2E/qFq2g=="],
|
||||
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g=="],
|
||||
"@next/env": ["@next/env@15.3.3", "", {}, "sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw=="],
|
||||
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w=="],
|
||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg=="],
|
||||
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA=="],
|
||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw=="],
|
||||
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg=="],
|
||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw=="],
|
||||
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg=="],
|
||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA=="],
|
||||
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w=="],
|
||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw=="],
|
||||
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ=="],
|
||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA=="],
|
||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ=="],
|
||||
|
||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw=="],
|
||||
|
||||
"@schummar/icu-type-parser": ["@schummar/icu-type-parser@1.21.5", "", {}, "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="],
|
||||
|
||||
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
|
||||
|
||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.7", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.7" } }, "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g=="],
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.7", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.7", "@tailwindcss/oxide-darwin-arm64": "4.1.7", "@tailwindcss/oxide-darwin-x64": "4.1.7", "@tailwindcss/oxide-freebsd-x64": "4.1.7", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.7", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.7", "@tailwindcss/oxide-linux-arm64-musl": "4.1.7", "@tailwindcss/oxide-linux-x64-gnu": "4.1.7", "@tailwindcss/oxide-linux-x64-musl": "4.1.7", "@tailwindcss/oxide-wasm32-wasi": "4.1.7", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.7", "@tailwindcss/oxide-win32-x64-msvc": "4.1.7" } }, "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ=="],
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.8", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.8", "@tailwindcss/oxide-darwin-arm64": "4.1.8", "@tailwindcss/oxide-darwin-x64": "4.1.8", "@tailwindcss/oxide-freebsd-x64": "4.1.8", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.8", "@tailwindcss/oxide-linux-arm64-musl": "4.1.8", "@tailwindcss/oxide-linux-x64-gnu": "4.1.8", "@tailwindcss/oxide-linux-x64-musl": "4.1.8", "@tailwindcss/oxide-wasm32-wasi": "4.1.8", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.8", "@tailwindcss/oxide-win32-x64-msvc": "4.1.8" } }, "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.7", "", { "os": "android", "cpu": "arm64" }, "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg=="],
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg=="],
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw=="],
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw=="],
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7", "", { "os": "linux", "cpu": "arm" }, "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g=="],
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8", "", { "os": "linux", "cpu": "arm" }, "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA=="],
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A=="],
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg=="],
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA=="],
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.8", "", { "os": "linux", "cpu": "x64" }, "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.7", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.9", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.8", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.10", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw=="],
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ=="],
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.8", "", { "os": "win32", "cpu": "x64" }, "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ=="],
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.7", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.7", "@tailwindcss/oxide": "4.1.7", "postcss": "^8.4.41", "tailwindcss": "4.1.7" } }, "sha512-88g3qmNZn7jDgrrcp3ZXEQfp9CVox7xjP1HN2TFKI03CltPVd/c61ydn5qJJL8FYunn0OqBaW5HNUga0kmPVvw=="],
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.8", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.8", "@tailwindcss/oxide": "4.1.8", "postcss": "^8.4.41", "tailwindcss": "4.1.8" } }, "sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw=="],
|
||||
|
||||
"@types/node": ["@types/node@20.17.50", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A=="],
|
||||
"@types/chai": ["@types/chai@4.3.20", "", {}, "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.5", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g=="],
|
||||
"@types/mocha": ["@types/mocha@10.0.10", "", {}, "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q=="],
|
||||
|
||||
"@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.6", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="],
|
||||
|
||||
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
|
||||
|
||||
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"assertion-error": ["assertion-error@1.1.0", "", {}, "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="],
|
||||
|
||||
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001718", "", {}, "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw=="],
|
||||
|
||||
"chai": ["chai@4.5.0", "", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="],
|
||||
|
||||
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
|
||||
"check-error": ["check-error@1.0.3", "", { "dependencies": { "get-func-name": "^2.0.2" } }, "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg=="],
|
||||
|
||||
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
@ -155,22 +234,132 @@
|
||||
|
||||
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||
|
||||
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
|
||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="],
|
||||
|
||||
"decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],
|
||||
|
||||
"deep-eql": ["deep-eql@4.1.4", "", { "dependencies": { "type-detect": "^4.0.0" } }, "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
|
||||
|
||||
"diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
|
||||
|
||||
"framer-motion": ["framer-motion@12.12.2", "", { "dependencies": { "motion-dom": "^12.12.1", "motion-utils": "^12.12.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-qCszZCiGWkilL40E3VuhIJJC/CS3SIBl2IHyGK8FU30nOUhTmhBNWPrNFyozAWH/bXxwzi19vJHIGVdALF0LCg=="],
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="],
|
||||
|
||||
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
|
||||
|
||||
"form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="],
|
||||
|
||||
"framer-motion": ["framer-motion@12.15.0", "", { "dependencies": { "motion-dom": "^12.15.0", "motion-utils": "^12.12.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-func-name": ["get-func-name@2.0.2", "", {}, "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
||||
|
||||
"i18next": ["i18next@25.2.1", "", { "dependencies": { "@babel/runtime": "^7.27.1" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"intl-messageformat": ["intl-messageformat@10.7.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.4", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.2", "tslib": "^2.8.0" } }, "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug=="],
|
||||
|
||||
"ioredis": ["ioredis@5.6.1", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||
|
||||
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
|
||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
|
||||
|
||||
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||
@ -193,62 +382,186 @@
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
|
||||
|
||||
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
|
||||
"logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
|
||||
|
||||
"loupe": ["loupe@2.3.7", "", { "dependencies": { "get-func-name": "^2.0.1" } }, "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="],
|
||||
|
||||
"mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.12.1", "", { "dependencies": { "motion-utils": "^12.12.1" } }, "sha512-GXq/uUbZBEiFFE+K1Z/sxdPdadMdfJ/jmBALDfIuHGi0NmtealLOfH9FqT+6aNPgVx8ilq0DtYmyQlo6Uj9LKQ=="],
|
||||
"mocha": ["mocha@10.8.2", "", { "dependencies": { "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", "chokidar": "^3.5.3", "debug": "^4.3.5", "diff": "^5.2.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^8.1.0", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^5.1.6", "ms": "^2.1.3", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^6.5.1", "yargs": "^16.2.0", "yargs-parser": "^20.2.9", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg=="],
|
||||
|
||||
"motion-dom": ["motion-dom@12.15.0", "", { "dependencies": { "motion-utils": "^12.12.1" } }, "sha512-D2ldJgor+2vdcrDtKJw48k3OddXiZN1dDLLWrS8kiHzQdYVruh0IoTwbJBslrnTXIPgFED7PBN2Zbwl7rNqnhA=="],
|
||||
|
||||
"motion-utils": ["motion-utils@12.12.1", "", {}, "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"next": ["next@15.3.2", "", { "dependencies": { "@next/env": "15.3.2", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.2", "@next/swc-darwin-x64": "15.3.2", "@next/swc-linux-arm64-gnu": "15.3.2", "@next/swc-linux-arm64-musl": "15.3.2", "@next/swc-linux-x64-gnu": "15.3.2", "@next/swc-linux-x64-musl": "15.3.2", "@next/swc-win32-arm64-msvc": "15.3.2", "@next/swc-win32-x64-msvc": "15.3.2", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ=="],
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"next": ["next@15.3.3", "", { "dependencies": { "@next/env": "15.3.3", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.3.3", "@next/swc-darwin-x64": "15.3.3", "@next/swc-linux-arm64-gnu": "15.3.3", "@next/swc-linux-arm64-musl": "15.3.3", "@next/swc-linux-x64-gnu": "15.3.3", "@next/swc-linux-x64-musl": "15.3.3", "@next/swc-win32-arm64-msvc": "15.3.3", "@next/swc-win32-x64-msvc": "15.3.3", "sharp": "^0.34.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw=="],
|
||||
|
||||
"next-intl": ["next-intl@4.1.0", "", { "dependencies": { "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^1.0.0", "use-intl": "^4.1.0" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", "typescript": "^5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"pathval": ["pathval@1.1.1", "", {}, "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
||||
|
||||
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
|
||||
|
||||
"property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
|
||||
|
||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||
|
||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
|
||||
|
||||
"redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
|
||||
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
|
||||
|
||||
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
|
||||
|
||||
"sharp": ["sharp@0.34.2", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.4", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.2", "@img/sharp-darwin-x64": "0.34.2", "@img/sharp-libvips-darwin-arm64": "1.1.0", "@img/sharp-libvips-darwin-x64": "1.1.0", "@img/sharp-libvips-linux-arm": "1.1.0", "@img/sharp-libvips-linux-arm64": "1.1.0", "@img/sharp-libvips-linux-ppc64": "1.1.0", "@img/sharp-libvips-linux-s390x": "1.1.0", "@img/sharp-libvips-linux-x64": "1.1.0", "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", "@img/sharp-libvips-linuxmusl-x64": "1.1.0", "@img/sharp-linux-arm": "0.34.2", "@img/sharp-linux-arm64": "0.34.2", "@img/sharp-linux-s390x": "0.34.2", "@img/sharp-linux-x64": "0.34.2", "@img/sharp-linuxmusl-arm64": "0.34.2", "@img/sharp-linuxmusl-x64": "0.34.2", "@img/sharp-wasm32": "0.34.2", "@img/sharp-win32-arm64": "0.34.2", "@img/sharp-win32-ia32": "0.34.2", "@img/sharp-win32-x64": "0.34.2" } }, "sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
|
||||
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"swr": ["swr@2.3.3", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.7", "", {}, "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg=="],
|
||||
"tailwindcss": ["tailwindcss@4.1.8", "", {}, "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og=="],
|
||||
|
||||
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
|
||||
|
||||
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
||||
|
||||
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
|
||||
|
||||
"tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="],
|
||||
|
||||
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"type-detect": ["type-detect@4.1.0", "", {}, "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw=="],
|
||||
|
||||
"type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
|
||||
|
||||
"use-intl": ["use-intl@4.1.0", "", { "dependencies": { "@formatjs/fast-memoize": "^2.2.0", "@schummar/icu-type-parser": "1.21.5", "intl-messageformat": "^10.5.14" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" } }, "sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
|
||||
|
||||
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
|
||||
|
||||
"workerpool": ["workerpool@6.5.1", "", {}, "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
|
||||
|
||||
"yargs-unparser": ["yargs-unparser@2.0.0", "", { "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" } }, "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"yup": ["yup@1.6.1", "", { "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", "toposort": "^2.0.2", "type-fest": "^2.19.0" } }, "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA=="],
|
||||
|
||||
"@formatjs/ecma402-abstract/@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.1", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
@ -261,6 +574,16 @@
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"colorspace/color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
|
||||
|
||||
"log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
||||
|
||||
"colorspace/color/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"colorspace/color/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
}
|
||||
}
|
||||
|
51
packages/next/components/hooks/useCaptcha.ts
Normal file
51
packages/next/components/hooks/useCaptcha.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { useState } from "react";
|
||||
import type { CaptchaVerificationRawResponse, CaptchaSessionRawResponse } from "@backend/src/schema";
|
||||
import { fetcher } from "@/lib/net";
|
||||
import { computeVdfInWorker } from "@/lib/vdf";
|
||||
|
||||
interface UseCaptchaOptions {
|
||||
backendURL: string;
|
||||
route: string;
|
||||
}
|
||||
|
||||
export function useCaptcha({ backendURL, route }: UseCaptchaOptions) {
|
||||
const fullUrl = `${backendURL}/captcha/session`;
|
||||
const [isUsed, setIsUsed] = useState(false);
|
||||
|
||||
const { trigger, data, isMutating, error } = useSWRMutation<CaptchaVerificationRawResponse, Error>(
|
||||
fullUrl,
|
||||
async (url: string) => {
|
||||
const sessionRes = await fetcher<CaptchaSessionRawResponse>(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
data: { route }
|
||||
});
|
||||
|
||||
const { g, n, t, id } = sessionRes;
|
||||
if (!g || !n || !t || !id) {
|
||||
throw new Error("Missing required CAPTCHA parameters");
|
||||
}
|
||||
|
||||
const ans = await computeVdfInWorker(BigInt(g), BigInt(n), BigInt(t));
|
||||
|
||||
const resultUrl = new URL(`${backendURL}/captcha/${id}/result`);
|
||||
resultUrl.searchParams.set("ans", ans.result.toString());
|
||||
|
||||
const result = await fetcher<CaptchaVerificationRawResponse>(resultUrl.toString());
|
||||
setIsUsed(false);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
startCaptcha: trigger,
|
||||
captchaResult: data,
|
||||
isLoadingCaptcha: isMutating,
|
||||
captchaError: error,
|
||||
captchaUsed: isUsed,
|
||||
setCaptchaUsedState: setIsUsed
|
||||
};
|
||||
}
|
@ -15,9 +15,14 @@ import { SearchIcon } from "@/components/icons/SearchIcon";
|
||||
import { InfoIcon } from "@/components/icons/InfoIcon";
|
||||
import { HomeIcon } from "@/components/icons/HomeIcon";
|
||||
import { TextButton } from "@/components/ui/Buttons/TextButton";
|
||||
import Link from "next/link";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { UserResponse } from "@backend/src/schema";
|
||||
|
||||
export const HeaderDestop = () => {
|
||||
interface HeaderProps {
|
||||
user?: UserResponse;
|
||||
}
|
||||
|
||||
export const HeaderDestop = ({ user }: HeaderProps) => {
|
||||
return (
|
||||
<div className="hidden md:flex relative top-0 left-0 w-full h-28 z-20 justify-between">
|
||||
<div className="w-[305px] xl:ml-8 inline-flex items-center">
|
||||
@ -37,14 +42,19 @@ export const HeaderDestop = () => {
|
||||
className="inline-flex relative gap-6 h-full lg:right-12
|
||||
text-xl font-medium items-center w-[15rem] min-w-[8rem] mr-4 lg:mr-0 lg:w-[305px] justify-end"
|
||||
>
|
||||
<a href="/signup">注册</a>
|
||||
{user ? (
|
||||
<Link href="/my/profile">{user.nickname || user.username}</Link>
|
||||
) : (
|
||||
<Link href="/signup">注册</Link>
|
||||
)}
|
||||
|
||||
<a href="/about">关于</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const HeaderMobile = () => {
|
||||
export const HeaderMobile = ({ user }: HeaderProps) => {
|
||||
const [showDrawer, setShowDrawer] = useState(false);
|
||||
const [showsearchBox, setShowsearchBox] = useState(false);
|
||||
|
||||
@ -101,14 +111,14 @@ export const HeaderMobile = () => {
|
||||
)}
|
||||
{!showsearchBox && (
|
||||
<div className="absolute left-1/2 -translate-x-1/2 -translate-y-0.5 inline-flex h-full items-center">
|
||||
<a href="/">
|
||||
<Link href="/">
|
||||
<DarkModeImage
|
||||
lightSrc={LogoMobileLight}
|
||||
darkSrc={LogoMobileDark}
|
||||
alt="Logo"
|
||||
className="w-24 h-8 translate-y-[2px]"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{showsearchBox && <SearchBox close={() => setShowsearchBox(false)} />}
|
||||
@ -125,11 +135,11 @@ export const HeaderMobile = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Header = () => {
|
||||
export const Header = (props: HeaderProps) => {
|
||||
return (
|
||||
<>
|
||||
<HeaderDestop />
|
||||
<HeaderMobile />
|
||||
<HeaderDestop {...props} />
|
||||
<HeaderMobile {...props} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,41 +1,86 @@
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import React from "react";
|
||||
import { TextButton } from "./Buttons/TextButton";
|
||||
import { useEffect } from "react";
|
||||
|
||||
interface DialogProps {
|
||||
export const useDisableBodyScroll = (open: boolean) => {
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "unset";
|
||||
}
|
||||
}, [open]);
|
||||
};
|
||||
|
||||
type OptionalChidrenProps<T = React.HTMLAttributes<HTMLElement>> = T & {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type HeadElementAttr = React.HTMLAttributes<HTMLHeadElement>;
|
||||
type DivElementAttr = React.HTMLAttributes<HTMLDivElement>;
|
||||
type ButtonElementAttr = React.HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
type DialogHeadlineProps = OptionalChidrenProps<HeadElementAttr>;
|
||||
type DialogSupportingTextProps = OptionalChidrenProps<DivElementAttr>;
|
||||
type DialogButtonGroupProps = OptionalChidrenProps<DivElementAttr>;
|
||||
|
||||
interface DialogButtonProps extends OptionalChidrenProps<ButtonElementAttr> {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
interface DialogProps extends OptionalChidrenProps<DivElementAttr> {
|
||||
show: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface OptionalChidrenProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
type DialogHeadlineProps = OptionalChidrenProps;
|
||||
type DialogSupportingTextProps = OptionalChidrenProps;
|
||||
type DialogButtonGroupProps = OptionalChidrenProps;
|
||||
|
||||
interface DialogButtonProps extends OptionalChidrenProps {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export const DialogHeadline: React.FC<DialogHeadlineProps> = ({ children }: DialogHeadlineProps) => {
|
||||
return <h2 className="text-2xl leading-8 text-on-surface dark:text-dark-on-surface">{children}</h2>;
|
||||
export const DialogHeadline: React.FC<DialogHeadlineProps> = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: DialogHeadlineProps) => {
|
||||
return (
|
||||
<h2 className={"text-2xl leading-8 text-on-surface dark:text-dark-on-surface " + className || ""} {...rest}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
};
|
||||
|
||||
export const DialogSupportingText: React.FC<DialogSupportingTextProps> = ({ children }: DialogHeadlineProps) => {
|
||||
return <div className="mt-4 text-sm leading-5 mb-6">{children}</div>;
|
||||
export const DialogSupportingText: React.FC<DialogSupportingTextProps> = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: DialogHeadlineProps) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"mt-4 text-sm leading-5 mb-6 text-on-surface-variant dark:text-dark-on-surface-variant " + className ||
|
||||
""
|
||||
}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DialogButton: React.FC<DialogButtonProps> = ({ children, onClick }: DialogButtonProps) => {
|
||||
return <TextButton onClick={onClick}>{children}</TextButton>;
|
||||
export const DialogButton: React.FC<DialogButtonProps> = ({ children, onClick, ...rest }: DialogButtonProps) => {
|
||||
return (
|
||||
<TextButton onClick={onClick} {...rest}>
|
||||
{children}
|
||||
</TextButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const DialogButtonGroup: React.FC<DialogButtonGroupProps> = ({ children }: DialogButtonGroupProps) => {
|
||||
return <div className="flex justify-end gap-2">{children}</div>;
|
||||
export const DialogButtonGroup: React.FC<DialogButtonGroupProps> = ({ children, ...rest }: DialogButtonGroupProps) => {
|
||||
return (
|
||||
<div className="flex justify-end gap-2" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Dialog: React.FC<DialogProps> = ({ show, children }: DialogProps) => {
|
||||
export const Dialog: React.FC<DialogProps> = ({ show, children, className }: DialogProps) => {
|
||||
useDisableBodyScroll(show);
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
@ -49,12 +94,13 @@ export const Dialog: React.FC<DialogProps> = ({ show, children }: DialogProps) =
|
||||
transition={{ duration: 0.3 }}
|
||||
/>
|
||||
<motion.div
|
||||
className="fixed min-w-[17.5rem] sm:max-w-[35rem] h-auto z-50 bg-surface-container-high
|
||||
shadow-2xl shadow-shadow/15 rounded-[1.75rem] p-6 dark:bg-dark-surface-container-high mx-2"
|
||||
className={`fixed min-w-[17.5rem] sm:max-w-[35rem] h-auto z-50 bg-surface-container-high
|
||||
shadow-2xl shadow-shadow/15 rounded-[1.75rem] p-6 dark:bg-dark-surface-container-high mx-2 ${className}`}
|
||||
initial={{ opacity: 0.5, transform: "scale(1.1)" }}
|
||||
animate={{ opacity: 1, transform: "scale(1)" }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ ease: [0.31, 0.69, 0.3, 1.02], duration: 0.3 }}
|
||||
aria-modal="true"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
|
20
packages/next/components/utils/LocalizedRichText.tsx
Normal file
20
packages/next/components/utils/LocalizedRichText.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
// These tags are available
|
||||
type Tag = "p" | "b" | "i";
|
||||
|
||||
type Props = {
|
||||
children(tags: Record<Tag, (chunks: ReactNode) => ReactNode>): ReactNode;
|
||||
};
|
||||
|
||||
export default function LocalizedRichText({ children }: Props) {
|
||||
return (
|
||||
<div className="prose">
|
||||
{children({
|
||||
p: (chunks: ReactNode) => <p>{chunks}</p>,
|
||||
b: (chunks: ReactNode) => <b className="font-semibold">{chunks}</b>,
|
||||
i: (chunks: ReactNode) => <i className="italic">{chunks}</i>
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
6
packages/next/i18n/navigation.ts
Normal file
6
packages/next/i18n/navigation.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { createNavigation } from "next-intl/navigation";
|
||||
import { routing } from "./routing";
|
||||
|
||||
// Lightweight wrappers around Next.js' navigation
|
||||
// APIs that consider the routing configuration
|
||||
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);
|
21
packages/next/i18n/request.ts
Normal file
21
packages/next/i18n/request.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { hasLocale } from "next-intl";
|
||||
import { routing } from "./routing";
|
||||
import zh from "./strings/zh.json";
|
||||
import en from "./strings/en.json";
|
||||
|
||||
const stringsMap = {
|
||||
zh: zh,
|
||||
en: en
|
||||
};
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
// Typically corresponds to the `[locale]` segment
|
||||
const requested = await requestLocale;
|
||||
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: stringsMap[locale]
|
||||
};
|
||||
});
|
11
packages/next/i18n/routing.ts
Normal file
11
packages/next/i18n/routing.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { defineRouting } from "next-intl/routing";
|
||||
|
||||
export const routing = defineRouting({
|
||||
// A list of all locales that are supported
|
||||
locales: ["en", "zh"],
|
||||
|
||||
// Used when no locale matches
|
||||
defaultLocale: "zh",
|
||||
|
||||
localePrefix: "as-needed"
|
||||
});
|
6
packages/next/i18n/strings/en.json
Normal file
6
packages/next/i18n/strings/en.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"yup_errors": {
|
||||
"field_too_short": "{field}至少需要{min}个字符。",
|
||||
"field_too_long": "{field}不能多于{max}个字符。"
|
||||
}
|
||||
}
|
32
packages/next/i18n/strings/zh.json
Normal file
32
packages/next/i18n/strings/zh.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"yup_errors": {
|
||||
"field_too_short": "{field}至少需要 {min} 个字符。",
|
||||
"field_too_big": "{field}不能多于 {max} 个字符。",
|
||||
"field_required": "请填写{field}。"
|
||||
},
|
||||
"username": "用户名",
|
||||
"password": "密码",
|
||||
"nickname": "昵称",
|
||||
"backend": {
|
||||
"error": {
|
||||
"captcha_failed": "无法完成安全验证。",
|
||||
"user_exists": "用户名 “{username}” 已被占用。",
|
||||
"user_not_found_after_register": "我们的服务器出现错误:找不到名为'{username}'的用户。请联系我们的<support>支持团队</support>,反馈此问题。",
|
||||
"captcha_not_found": "无法完成安全验证。你可以刷新页面并重试。如果此问题反复出现,你可以联系我们的<support>支持团队</support>,反馈此问题。"
|
||||
},
|
||||
"error_code": {
|
||||
"INVALID_QUERY_PARAMS": "查询无效",
|
||||
"UNKNOWN_ERROR": "未知错误",
|
||||
"INVALID_PAYLOAD": "请求格式错误",
|
||||
"INVALID_FORMAT": "数据格式错误",
|
||||
"INVALID_HEADER": "请求头无效",
|
||||
"BODY_TOO_LARGE": "请求体过大",
|
||||
"UNAUTHORIZED": "未授权",
|
||||
"INVALID_CREDENTIALS": "凭证无效",
|
||||
"ENTITY_NOT_FOUND": "实体不存在",
|
||||
"SERVER_ERROR": "服务器错误",
|
||||
"RATE_LIMIT_EXCEEDED": "请求过于频繁",
|
||||
"ENTITY_EXISTS": "实体已存在"
|
||||
}
|
||||
}
|
||||
}
|
19
packages/next/lib/db/user.ts
Normal file
19
packages/next/lib/db/user.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { DatabaseUserType, sqlCred } from "@cvsa/core";
|
||||
|
||||
export const getUserBySession = async (sessionID: string) => {
|
||||
const users = await sqlCred<DatabaseUserType[]>`
|
||||
SELECT u.*
|
||||
FROM users u
|
||||
JOIN login_sessions ls ON u.id = ls.uid
|
||||
WHERE ls.id = ${sessionID};
|
||||
`;
|
||||
if (users.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const user = users[0];
|
||||
return {
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
role: user.role
|
||||
};
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
import axios, { AxiosRequestConfig, AxiosError, Method } from "axios";
|
||||
|
||||
export class ApiRequestError extends Error {
|
||||
public code: number | undefined;
|
||||
public response: unknown | undefined;
|
||||
@ -8,3 +10,48 @@ export class ApiRequestError extends Error {
|
||||
this.response = res;
|
||||
}
|
||||
}
|
||||
|
||||
type HttpMethod = Extract<Method, "GET" | "POST" | "PUT" | "DELETE" | "PATCH">;
|
||||
|
||||
const httpMethods = {
|
||||
get: axios.get,
|
||||
post: axios.post,
|
||||
put: axios.put,
|
||||
delete: axios.delete,
|
||||
patch: axios.patch
|
||||
};
|
||||
|
||||
export async function fetcher<JSON = unknown>(
|
||||
url: string,
|
||||
init?: Omit<AxiosRequestConfig, "method"> & { method?: HttpMethod }
|
||||
): Promise<JSON> {
|
||||
const { method = "get", data, ...config } = init || {};
|
||||
|
||||
const fullConfig: AxiosRequestConfig = {
|
||||
method,
|
||||
...config,
|
||||
timeout: 10000
|
||||
};
|
||||
|
||||
try {
|
||||
const m = method.toLowerCase() as keyof typeof httpMethods;
|
||||
if (["post", "patch", "put"].includes(m)) {
|
||||
const response = await httpMethods[m](url, data, fullConfig);
|
||||
return response.data;
|
||||
} else {
|
||||
const response = await httpMethods[m](url, fullConfig);
|
||||
return response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError;
|
||||
|
||||
if (axiosError.response) {
|
||||
const { status, data } = axiosError.response;
|
||||
throw new ApiRequestError(`HTTP error! status: ${status}`, data, status);
|
||||
} else if (axiosError.request) {
|
||||
throw new ApiRequestError("No response received", undefined, -1);
|
||||
} else {
|
||||
throw new ApiRequestError(axiosError.message || "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
packages/next/middleware.ts
Normal file
11
packages/next/middleware.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import createMiddleware from "next-intl/middleware";
|
||||
import { routing } from "@/i18n/routing";
|
||||
|
||||
export default createMiddleware(routing);
|
||||
|
||||
export const config = {
|
||||
// Match all pathnames except for
|
||||
// - … if they start with `/api`, `/trpc`, `/_next` or `/_vercel`
|
||||
// - … the ones containing a dot (e.g. `favicon.ico`)
|
||||
matcher: "/((?!api|trpc|_next|_vercel|.*\\..*).*)"
|
||||
};
|
@ -1,7 +1,13 @@
|
||||
import type { NextConfig } from "next";
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
devIndicators: false
|
||||
devIndicators: false,
|
||||
experimental: {
|
||||
externalDir: true
|
||||
}
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
const withNextIntl = createNextIntlPlugin();
|
||||
|
||||
export default withNextIntl(nextConfig);
|
||||
|
@ -5,23 +5,28 @@
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack -p 7400",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"start": "next start -p 7400",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.12.2",
|
||||
"next": "^15.1.8",
|
||||
"@cvsa/core": "^0.0.1",
|
||||
"axios": "^1.9.0",
|
||||
"framer-motion": "^12.15.0",
|
||||
"i18next": "^25.2.1",
|
||||
"next": "^15.3.3",
|
||||
"next-intl": "^4.1.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"swr": "^2.3.3"
|
||||
"swr": "^2.3.3",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"tailwindcss": "^4"
|
||||
"typescript": "^5.8.3",
|
||||
"@types/node": "^20.17.57",
|
||||
"@types/react": "^19.1.6",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"tailwindcss": "^4.1.8"
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": ["./*"],
|
||||
"@backend/*": ["../backend/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
Loading…
Reference in New Issue
Block a user