docs-i18n 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/app/lib/auth-client.ts +21 -0
- package/admin/app/lib/auth.server.ts +62 -0
- package/admin/app/routeTree.gen.ts +21 -3
- package/admin/app/routes/index.tsx +68 -8
- package/admin/app/routes/login.tsx +207 -0
- package/admin/app/server.ts +23 -0
- package/admin/app/styles.css +146 -0
- package/admin/app/worker.ts +61 -0
- package/admin/dist/client/assets/auth-client-DqJZaFZR.js +3 -0
- package/admin/dist/client/assets/login-CQjkiBAF.js +1 -0
- package/admin/dist/client/assets/main-bc5tcdGg.js +18 -0
- package/admin/dist/client/assets/routes-CTPOG_v1.js +3 -0
- package/admin/dist/client/assets/{styles-DJ6QEJmN.css → styles-zS0BbO7e.css} +1 -1
- package/admin/dist/client/assets/useNavigate-CEOVvMjz.js +1 -0
- package/admin/dist/server/assets/{react-dom-CpO9xk_L.js → __tanstack-start-server-fn-resolver-N1N5mZVv.js} +83 -507
- package/admin/dist/server/assets/_tanstack-start-manifest_v-BgMVPPt2.js +21 -0
- package/admin/dist/server/assets/adapter-DjXlUL1J.js +2253 -0
- package/admin/dist/server/assets/auth-client-CoUpyQIM.js +807 -0
- package/admin/dist/server/assets/auth-dSAIPjBw.js +72 -0
- package/admin/dist/server/assets/auth.server-BLVDnTCZ.js +31352 -0
- package/admin/dist/server/assets/bun-sqlite-dialect-C8OaCWSL-BNNY-FoT.js +154 -0
- package/admin/dist/server/assets/compiled-query-CnFG_BVV.js +6967 -0
- package/admin/dist/server/assets/d1-sqlite-dialect-sYHNqBte-Bjz-cybU.js +115 -0
- package/admin/dist/server/assets/dist-C3-e8E2B.js +215 -0
- package/admin/dist/server/assets/dist-CUzFWZag.js +6039 -0
- package/admin/dist/server/assets/{init-DJr2glb3.js → dist-DBv71kqn.js} +21 -381
- package/admin/dist/server/assets/error-CASJ5tIm.js +457 -0
- package/admin/dist/server/assets/error-codes-BhMTGvV6.js +1153 -0
- package/admin/dist/server/assets/init-CJJUsPDL.js +49 -0
- package/admin/dist/server/assets/job-manager-D9Ab9hgu.js +179 -0
- package/admin/dist/server/assets/{jobs-bQfYqSk7.js → jobs-DrEe9YOj.js} +18 -5
- package/admin/dist/server/assets/kysely-adapter-YC8RFPyc.js +2 -0
- package/admin/dist/server/assets/login-SqzTMYOZ.js +228 -0
- package/admin/dist/server/assets/migrator-ZpVZslbq.js +2926 -0
- package/admin/dist/server/assets/{misc-DOk3t9vs.js → misc-BSoYldBT.js} +22 -9
- package/admin/dist/server/assets/{models-CBb8Owe5.js → models-CoviNHUP.js} +1 -1
- package/admin/dist/server/assets/node-sqlite-dialect-BJIaP6lL.js +154 -0
- package/admin/dist/server/assets/{router-DlU_fGDK.js → router-Cxm33tn3.js} +23 -11
- package/admin/dist/server/assets/{routes-pIM0fgUO.js → routes-CxGhew8Z.js} +56 -22
- package/admin/dist/server/assets/{routes-BcfX6iub.js → routes-DtWFPDUc.js} +43 -29
- package/admin/dist/server/assets/sqlite-adapter-CL2EidjD.js +69 -0
- package/admin/dist/server/assets/{start-BS52hm79.js → start-BiybVoR2.js} +1 -1
- package/admin/dist/server/assets/status-B1AGLvHn.js +162 -0
- package/admin/dist/server/assets/status-D7PU72hm.js +262 -0
- package/admin/dist/server/assets/string-B4XlckmJ.js +6 -0
- package/admin/dist/server/assets/useNavigate-0H08s_Q2.js +29 -0
- package/admin/dist/server/assets/useRouter-BXJvr8to.js +508 -0
- package/admin/dist/server/server.js +70 -90
- package/admin/package.json +6 -1
- package/admin/server/functions/auth.ts +89 -0
- package/admin/server/functions/jobs.ts +30 -2
- package/admin/server/functions/misc.ts +25 -5
- package/admin/server/functions/status.ts +52 -3
- package/admin/server/index.ts +1 -1
- package/admin/server/init.ts +21 -9
- package/admin/server/services/d1-status.ts +282 -0
- package/admin/vite.config.ts +29 -15
- package/admin/wrangler.jsonc +16 -0
- package/package.json +1 -1
- package/template/content/docs-i18n/en/cli.md +9 -5
- package/template/content/docs-i18n/en/deployment.md +85 -68
- package/admin/dist/client/assets/main-CSFhgz4p.js +0 -17
- package/admin/dist/client/assets/routes-C6bCOSX-.js +0 -3
- package/admin/dist/server/assets/_tanstack-start-manifest_v-BE5XHVZ2.js +0 -17
- package/admin/dist/server/assets/redirect-DtfSYi2g.js +0 -51
- package/admin/dist/server/assets/status-CZz8Rs_7.js +0 -81
- /package/admin/dist/server/assets/{createServerRpc-DJq9yo-B.js → createServerRpc-Cyyxq9HQ.js} +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { r as __require } from "./chunk-CNvmzFzq.js";
|
|
2
|
+
//#region server/init.ts
|
|
3
|
+
var _initialized = false;
|
|
4
|
+
var _config;
|
|
5
|
+
function isCfWorkers() {
|
|
6
|
+
return !!globalThis.__CF_WORKERS;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Initialize admin server services with the project config.
|
|
10
|
+
*/
|
|
11
|
+
async function initAdmin(config) {
|
|
12
|
+
_config = config;
|
|
13
|
+
if (!isCfWorkers()) {
|
|
14
|
+
const { initStatus } = await import("./status-B1AGLvHn.js");
|
|
15
|
+
const { setConfig } = await import("./job-manager-D9Ab9hgu.js");
|
|
16
|
+
initStatus(config);
|
|
17
|
+
setConfig(config);
|
|
18
|
+
}
|
|
19
|
+
_initialized = true;
|
|
20
|
+
}
|
|
21
|
+
function getConfig() {
|
|
22
|
+
return _config;
|
|
23
|
+
}
|
|
24
|
+
function getProjectRoot() {
|
|
25
|
+
if (isCfWorkers()) return "(cf-workers)";
|
|
26
|
+
if (process.env.DOCS_I18N_PROJECT_ROOT) return process.env.DOCS_I18N_PROJECT_ROOT;
|
|
27
|
+
try {
|
|
28
|
+
const { readFileSync } = __require("node:fs");
|
|
29
|
+
const { resolve } = __require("node:path");
|
|
30
|
+
return readFileSync(resolve(process.env.TMPDIR || process.env.TEMP || "/tmp", "docs-i18n-project-root"), "utf-8").trim();
|
|
31
|
+
} catch {}
|
|
32
|
+
return process.cwd();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Ensure admin is initialized. Lazy-loads config on first call.
|
|
36
|
+
* On CF Workers, this is a no-op (D1 doesn't need config).
|
|
37
|
+
*/
|
|
38
|
+
async function ensureInit() {
|
|
39
|
+
if (_initialized) return;
|
|
40
|
+
if (isCfWorkers()) {
|
|
41
|
+
_initialized = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (typeof window !== "undefined") return;
|
|
45
|
+
const mod = await import(`${getProjectRoot()}/docs-i18n.config.ts`);
|
|
46
|
+
await initAdmin(mod.default ?? mod);
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { ensureInit, getConfig, getProjectRoot };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
//#region server/services/job-manager.ts
|
|
5
|
+
function getProjectRoot() {
|
|
6
|
+
if (process.env.DOCS_I18N_PROJECT_ROOT) return process.env.DOCS_I18N_PROJECT_ROOT;
|
|
7
|
+
try {
|
|
8
|
+
return readFileSync(resolve(process.env.TMPDIR || process.env.TEMP || "/tmp", "docs-i18n-project-root"), "utf-8").trim();
|
|
9
|
+
} catch {}
|
|
10
|
+
return process.cwd();
|
|
11
|
+
}
|
|
12
|
+
var _config;
|
|
13
|
+
function setConfig(config) {
|
|
14
|
+
_config = config;
|
|
15
|
+
}
|
|
16
|
+
var JobManager = class {
|
|
17
|
+
jobs = /* @__PURE__ */ new Map();
|
|
18
|
+
processes = /* @__PURE__ */ new Map();
|
|
19
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
20
|
+
nextId = 1;
|
|
21
|
+
start(opts) {
|
|
22
|
+
for (const [, job] of this.jobs) if (job.lang === opts.lang && job.version === opts.version && job.status === "running") throw new Error(`Job already running for ${opts.lang}/${opts.version}`);
|
|
23
|
+
const id = `job-${this.nextId++}`;
|
|
24
|
+
const job = {
|
|
25
|
+
id,
|
|
26
|
+
lang: opts.lang,
|
|
27
|
+
version: opts.version,
|
|
28
|
+
project: opts.project ?? "",
|
|
29
|
+
status: "running",
|
|
30
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31
|
+
logLines: [],
|
|
32
|
+
translatedFiles: 0,
|
|
33
|
+
totalFiles: 0,
|
|
34
|
+
toTranslate: 0,
|
|
35
|
+
errorFiles: 0
|
|
36
|
+
};
|
|
37
|
+
this.jobs.set(id, job);
|
|
38
|
+
let projectArg = opts.project ?? "";
|
|
39
|
+
let versionArg = opts.version;
|
|
40
|
+
if (!projectArg && opts.version.includes("/")) {
|
|
41
|
+
const parts = opts.version.split("/");
|
|
42
|
+
projectArg = parts.slice(0, -1).join("/");
|
|
43
|
+
versionArg = parts[parts.length - 1];
|
|
44
|
+
}
|
|
45
|
+
const args = [
|
|
46
|
+
"translate",
|
|
47
|
+
"--lang",
|
|
48
|
+
opts.lang,
|
|
49
|
+
"--version",
|
|
50
|
+
versionArg,
|
|
51
|
+
"--max",
|
|
52
|
+
String(opts.max ?? 999),
|
|
53
|
+
"--concurrency",
|
|
54
|
+
String(opts.concurrency ?? 3)
|
|
55
|
+
];
|
|
56
|
+
if (projectArg) args.push("--project", projectArg);
|
|
57
|
+
if (opts.model) args.push("--model", opts.model);
|
|
58
|
+
if (opts.files?.length) args.push("--files", opts.files.join(","));
|
|
59
|
+
const apiKey = _config?.llm?.apiKey || process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY || "";
|
|
60
|
+
if (apiKey) args.push("--api-key", apiKey);
|
|
61
|
+
const projectRoot = getProjectRoot();
|
|
62
|
+
const childEnv = {
|
|
63
|
+
...process.env,
|
|
64
|
+
NO_TTY: "1",
|
|
65
|
+
FORCE_COLOR: "0"
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
const envContent = readFileSync(resolve(projectRoot, ".env"), "utf-8");
|
|
69
|
+
for (const line of envContent.split("\n")) {
|
|
70
|
+
const match = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)\s*$/);
|
|
71
|
+
if (match && !childEnv[match[1]]) childEnv[match[1]] = match[2];
|
|
72
|
+
}
|
|
73
|
+
} catch {}
|
|
74
|
+
const proc = spawn("npx", ["docs-i18n", ...args], {
|
|
75
|
+
cwd: projectRoot,
|
|
76
|
+
stdio: [
|
|
77
|
+
"ignore",
|
|
78
|
+
"pipe",
|
|
79
|
+
"pipe"
|
|
80
|
+
],
|
|
81
|
+
env: childEnv,
|
|
82
|
+
shell: true
|
|
83
|
+
});
|
|
84
|
+
this.processes.set(id, proc);
|
|
85
|
+
proc.stdout?.on("data", (d) => {
|
|
86
|
+
for (const line of d.toString().split("\n").filter(Boolean)) {
|
|
87
|
+
this.addLog(id, line);
|
|
88
|
+
this.parseProgress(job, line);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
proc.stderr?.on("data", (d) => {
|
|
92
|
+
for (const line of d.toString().split("\n").filter(Boolean)) this.addLog(id, line);
|
|
93
|
+
});
|
|
94
|
+
proc.on("exit", (code) => {
|
|
95
|
+
job.status = code === 0 ? "completed" : "failed";
|
|
96
|
+
job.exitCode = code;
|
|
97
|
+
job.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
98
|
+
job.currentFile = void 0;
|
|
99
|
+
this.addLog(id, `Process exited with code ${code}`);
|
|
100
|
+
this.processes.delete(id);
|
|
101
|
+
this.emit(id, {
|
|
102
|
+
type: "exit",
|
|
103
|
+
code
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
proc.on("error", (err) => {
|
|
107
|
+
job.status = "failed";
|
|
108
|
+
job.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
109
|
+
this.addLog(id, `Process error: ${err.message}`);
|
|
110
|
+
this.processes.delete(id);
|
|
111
|
+
this.emit(id, {
|
|
112
|
+
type: "exit",
|
|
113
|
+
code: -1
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return job;
|
|
117
|
+
}
|
|
118
|
+
cancel(id) {
|
|
119
|
+
const proc = this.processes.get(id);
|
|
120
|
+
const job = this.jobs.get(id);
|
|
121
|
+
if (!proc || !job) return false;
|
|
122
|
+
proc.kill("SIGTERM");
|
|
123
|
+
job.status = "cancelled";
|
|
124
|
+
job.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
125
|
+
this.processes.delete(id);
|
|
126
|
+
this.emit(id, {
|
|
127
|
+
type: "exit",
|
|
128
|
+
code: null
|
|
129
|
+
});
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
list() {
|
|
133
|
+
return [...this.jobs.values()].sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
134
|
+
}
|
|
135
|
+
get(id) {
|
|
136
|
+
return this.jobs.get(id);
|
|
137
|
+
}
|
|
138
|
+
remove(id) {
|
|
139
|
+
const job = this.jobs.get(id);
|
|
140
|
+
if (!job || job.status === "running") return false;
|
|
141
|
+
this.jobs.delete(id);
|
|
142
|
+
this.subscribers.delete(id);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
subscribe(id, callback) {
|
|
146
|
+
if (!this.subscribers.has(id)) this.subscribers.set(id, /* @__PURE__ */ new Set());
|
|
147
|
+
this.subscribers.get(id)?.add(callback);
|
|
148
|
+
return () => {
|
|
149
|
+
this.subscribers.get(id)?.delete(callback);
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
addLog(id, line) {
|
|
153
|
+
const job = this.jobs.get(id);
|
|
154
|
+
if (!job) return;
|
|
155
|
+
const entry = `[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false })}] ${line}`;
|
|
156
|
+
job.logLines = job.logLines ?? [];
|
|
157
|
+
job.logLines.push(entry);
|
|
158
|
+
if (job.logLines.length > 500) job.logLines.shift();
|
|
159
|
+
this.emit(id, {
|
|
160
|
+
type: "log",
|
|
161
|
+
data: entry
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
emit(id, event) {
|
|
165
|
+
this.subscribers.get(id)?.forEach((cb) => cb(event));
|
|
166
|
+
}
|
|
167
|
+
parseProgress(job, line) {
|
|
168
|
+
const cached = line.match(/\+(\d+) cached/);
|
|
169
|
+
if (cached) job.translatedFiles += Number.parseInt(cached[1], 10);
|
|
170
|
+
const untranslated = line.match(/(\d+) untranslated keys/);
|
|
171
|
+
if (untranslated) job.toTranslate = Number.parseInt(untranslated[1], 10);
|
|
172
|
+
const chunk = line.match(/chunk \d+\/(\d+)/);
|
|
173
|
+
if (chunk) job.totalFiles = Number.parseInt(chunk[1], 10);
|
|
174
|
+
if (line.includes("⏳")) job.currentFile = line.replace(/^⏳\s*/, "");
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var jobManager = new JobManager();
|
|
178
|
+
//#endregion
|
|
179
|
+
export { jobManager, setConfig };
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { t as createServerFn } from "../server.js";
|
|
2
|
-
import { t as createServerRpc } from "./createServerRpc-
|
|
3
|
-
import { ensureInit, l as jobManager } from "./init-DJr2glb3.js";
|
|
2
|
+
import { t as createServerRpc } from "./createServerRpc-Cyyxq9HQ.js";
|
|
4
3
|
//#region server/functions/jobs.ts?tss-serverfn-split
|
|
4
|
+
function isCfWorkers() {
|
|
5
|
+
return !!globalThis.__CF_WORKERS;
|
|
6
|
+
}
|
|
7
|
+
async function getJobManager() {
|
|
8
|
+
const { jobManager } = await import("./job-manager-D9Ab9hgu.js");
|
|
9
|
+
return jobManager;
|
|
10
|
+
}
|
|
5
11
|
var fetchJobs_createServerFn_handler = createServerRpc({
|
|
6
12
|
id: "421de02ce39dde6e27cf4689e837ec072cbd01e63f8cdd5c2a3f42f0bd5ca613",
|
|
7
13
|
name: "fetchJobs",
|
|
8
14
|
filename: "server/functions/jobs.ts"
|
|
9
15
|
}, (opts) => fetchJobs.__executeServer(opts));
|
|
10
16
|
var fetchJobs = createServerFn({ method: "GET" }).handler(fetchJobs_createServerFn_handler, async () => {
|
|
17
|
+
if (isCfWorkers()) return [];
|
|
18
|
+
const { ensureInit } = await import("./init-CJJUsPDL.js");
|
|
11
19
|
await ensureInit();
|
|
12
|
-
return
|
|
20
|
+
return (await getJobManager()).list().map((j) => ({
|
|
13
21
|
...j,
|
|
14
22
|
logLines: (j.logLines ?? []).slice(-20)
|
|
15
23
|
}));
|
|
@@ -20,9 +28,11 @@ var createJob_createServerFn_handler = createServerRpc({
|
|
|
20
28
|
filename: "server/functions/jobs.ts"
|
|
21
29
|
}, (opts) => createJob.__executeServer(opts));
|
|
22
30
|
var createJob = createServerFn({ method: "POST" }).inputValidator((d) => d).handler(createJob_createServerFn_handler, async ({ data }) => {
|
|
31
|
+
if (isCfWorkers()) throw new Error("Job creation is not available on CF Workers");
|
|
32
|
+
const { ensureInit } = await import("./init-CJJUsPDL.js");
|
|
23
33
|
await ensureInit();
|
|
24
34
|
if (!data.lang || !data.version) throw new Error("Missing lang or version");
|
|
25
|
-
return
|
|
35
|
+
return (await getJobManager()).start(data);
|
|
26
36
|
});
|
|
27
37
|
var fetchJob_createServerFn_handler = createServerRpc({
|
|
28
38
|
id: "8a56694c9d7b29422a3e7d2f6b803be100d79d3853d92d465cb55ed572781e62",
|
|
@@ -30,7 +40,8 @@ var fetchJob_createServerFn_handler = createServerRpc({
|
|
|
30
40
|
filename: "server/functions/jobs.ts"
|
|
31
41
|
}, (opts) => fetchJob.__executeServer(opts));
|
|
32
42
|
var fetchJob = createServerFn({ method: "GET" }).inputValidator((d) => d).handler(fetchJob_createServerFn_handler, async ({ data }) => {
|
|
33
|
-
|
|
43
|
+
if (isCfWorkers()) throw new Error("Jobs not available on CF Workers");
|
|
44
|
+
const job = (await getJobManager()).get(data.id);
|
|
34
45
|
if (!job) throw new Error("Job not found");
|
|
35
46
|
return job;
|
|
36
47
|
});
|
|
@@ -40,6 +51,8 @@ var deleteJob_createServerFn_handler = createServerRpc({
|
|
|
40
51
|
filename: "server/functions/jobs.ts"
|
|
41
52
|
}, (opts) => deleteJob.__executeServer(opts));
|
|
42
53
|
var deleteJob = createServerFn({ method: "POST" }).inputValidator((d) => d).handler(deleteJob_createServerFn_handler, async ({ data }) => {
|
|
54
|
+
if (isCfWorkers()) throw new Error("Jobs not available on CF Workers");
|
|
55
|
+
const jobManager = await getJobManager();
|
|
43
56
|
const job = jobManager.get(data.id);
|
|
44
57
|
if (!job) throw new Error("Job not found");
|
|
45
58
|
if (job.status === "running") jobManager.cancel(data.id);
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { i as __toESM } from "./chunk-CNvmzFzq.js";
|
|
2
|
+
import { l as require_react, r as require_jsx_runtime } from "./useRouter-BXJvr8to.js";
|
|
3
|
+
import { t as useNavigate } from "./useNavigate-0H08s_Q2.js";
|
|
4
|
+
import { t as authClient } from "./auth-client-CoUpyQIM.js";
|
|
5
|
+
//#region app/routes/login.tsx?tsr-split=component
|
|
6
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
7
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
8
|
+
function LoginPage() {
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
const [mode, setMode] = (0, import_react.useState)("login");
|
|
11
|
+
const [username, setUsername] = (0, import_react.useState)("");
|
|
12
|
+
const [password, setPassword] = (0, import_react.useState)("");
|
|
13
|
+
const [email, setEmail] = (0, import_react.useState)("");
|
|
14
|
+
const [name, setName] = (0, import_react.useState)("");
|
|
15
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
16
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
17
|
+
const handleLogin = async (e) => {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
setError(null);
|
|
20
|
+
setLoading(true);
|
|
21
|
+
try {
|
|
22
|
+
const { error: authError } = await authClient.signIn.username({
|
|
23
|
+
username,
|
|
24
|
+
password
|
|
25
|
+
});
|
|
26
|
+
if (authError) {
|
|
27
|
+
setError(authError.message || "Login failed");
|
|
28
|
+
setLoading(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
navigate({ to: "/" });
|
|
32
|
+
} catch (err) {
|
|
33
|
+
setError(err instanceof Error ? err.message : "Login failed");
|
|
34
|
+
setLoading(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const handleRegister = async (e) => {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
setError(null);
|
|
40
|
+
setLoading(true);
|
|
41
|
+
try {
|
|
42
|
+
const { error: authError } = await authClient.signUp.email({
|
|
43
|
+
email,
|
|
44
|
+
password,
|
|
45
|
+
name: name || username,
|
|
46
|
+
username
|
|
47
|
+
});
|
|
48
|
+
if (authError) {
|
|
49
|
+
setError(authError.message || "Registration failed");
|
|
50
|
+
setLoading(false);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
navigate({ to: "/" });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
setError(err instanceof Error ? err.message : "Registration failed");
|
|
56
|
+
setLoading(false);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const handleAnonymousLogin = async () => {
|
|
60
|
+
setError(null);
|
|
61
|
+
setLoading(true);
|
|
62
|
+
try {
|
|
63
|
+
const { error: authError } = await authClient.signIn.anonymous();
|
|
64
|
+
if (authError) {
|
|
65
|
+
setError(authError.message || "Anonymous login failed");
|
|
66
|
+
setLoading(false);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
navigate({ to: "/" });
|
|
70
|
+
} catch (err) {
|
|
71
|
+
setError(err instanceof Error ? err.message : "Anonymous login failed");
|
|
72
|
+
setLoading(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
76
|
+
className: "login-page",
|
|
77
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
78
|
+
className: "login-card",
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Translation Admin" }),
|
|
81
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
82
|
+
className: "login-subtitle",
|
|
83
|
+
children: "Sign in to manage translations"
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
86
|
+
className: "login-mode-toggle",
|
|
87
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
88
|
+
type: "button",
|
|
89
|
+
className: mode === "login" ? "active" : "",
|
|
90
|
+
onClick: () => {
|
|
91
|
+
setMode("login");
|
|
92
|
+
setError(null);
|
|
93
|
+
},
|
|
94
|
+
children: "Sign In"
|
|
95
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
96
|
+
type: "button",
|
|
97
|
+
className: mode === "register" ? "active" : "",
|
|
98
|
+
onClick: () => {
|
|
99
|
+
setMode("register");
|
|
100
|
+
setError(null);
|
|
101
|
+
},
|
|
102
|
+
children: "Register"
|
|
103
|
+
})]
|
|
104
|
+
}),
|
|
105
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
106
|
+
className: "login-error",
|
|
107
|
+
children: error
|
|
108
|
+
}),
|
|
109
|
+
mode === "login" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
110
|
+
onSubmit: handleLogin,
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
113
|
+
htmlFor: "login-username",
|
|
114
|
+
children: "Username"
|
|
115
|
+
}),
|
|
116
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
117
|
+
id: "login-username",
|
|
118
|
+
type: "text",
|
|
119
|
+
value: username,
|
|
120
|
+
onChange: (e) => setUsername(e.target.value),
|
|
121
|
+
placeholder: "admin",
|
|
122
|
+
required: true,
|
|
123
|
+
autoComplete: "username"
|
|
124
|
+
}),
|
|
125
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
126
|
+
htmlFor: "login-password",
|
|
127
|
+
children: "Password"
|
|
128
|
+
}),
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
130
|
+
id: "login-password",
|
|
131
|
+
type: "password",
|
|
132
|
+
value: password,
|
|
133
|
+
onChange: (e) => setPassword(e.target.value),
|
|
134
|
+
placeholder: "password",
|
|
135
|
+
required: true,
|
|
136
|
+
autoComplete: "current-password"
|
|
137
|
+
}),
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
139
|
+
type: "submit",
|
|
140
|
+
className: "btn login-btn",
|
|
141
|
+
disabled: loading,
|
|
142
|
+
children: loading ? "Signing in..." : "Sign In"
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
146
|
+
onSubmit: handleRegister,
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
149
|
+
htmlFor: "reg-username",
|
|
150
|
+
children: "Username"
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
153
|
+
id: "reg-username",
|
|
154
|
+
type: "text",
|
|
155
|
+
value: username,
|
|
156
|
+
onChange: (e) => setUsername(e.target.value),
|
|
157
|
+
placeholder: "username",
|
|
158
|
+
required: true,
|
|
159
|
+
autoComplete: "username"
|
|
160
|
+
}),
|
|
161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
162
|
+
htmlFor: "reg-name",
|
|
163
|
+
children: "Display Name"
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
166
|
+
id: "reg-name",
|
|
167
|
+
type: "text",
|
|
168
|
+
value: name,
|
|
169
|
+
onChange: (e) => setName(e.target.value),
|
|
170
|
+
placeholder: "John Doe",
|
|
171
|
+
autoComplete: "name"
|
|
172
|
+
}),
|
|
173
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
174
|
+
htmlFor: "reg-email",
|
|
175
|
+
children: "Email"
|
|
176
|
+
}),
|
|
177
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
178
|
+
id: "reg-email",
|
|
179
|
+
type: "email",
|
|
180
|
+
value: email,
|
|
181
|
+
onChange: (e) => setEmail(e.target.value),
|
|
182
|
+
placeholder: "user@example.com",
|
|
183
|
+
required: true,
|
|
184
|
+
autoComplete: "email"
|
|
185
|
+
}),
|
|
186
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
187
|
+
htmlFor: "reg-password",
|
|
188
|
+
children: "Password"
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
191
|
+
id: "reg-password",
|
|
192
|
+
type: "password",
|
|
193
|
+
value: password,
|
|
194
|
+
onChange: (e) => setPassword(e.target.value),
|
|
195
|
+
placeholder: "password",
|
|
196
|
+
required: true,
|
|
197
|
+
minLength: 4,
|
|
198
|
+
autoComplete: "new-password"
|
|
199
|
+
}),
|
|
200
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
201
|
+
type: "submit",
|
|
202
|
+
className: "btn login-btn",
|
|
203
|
+
disabled: loading,
|
|
204
|
+
children: loading ? "Creating account..." : "Create Account"
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
}),
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
209
|
+
className: "login-divider",
|
|
210
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "or" })
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
213
|
+
type: "button",
|
|
214
|
+
className: "btn btn-outline login-btn",
|
|
215
|
+
onClick: handleAnonymousLogin,
|
|
216
|
+
disabled: loading,
|
|
217
|
+
children: "Continue as Guest"
|
|
218
|
+
}),
|
|
219
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
220
|
+
className: "login-hint",
|
|
221
|
+
children: "Guests can view translations but cannot make changes."
|
|
222
|
+
})
|
|
223
|
+
]
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
export { LoginPage as component };
|