From 7853e55a785dc34d178889ff13708875269f1d43 Mon Sep 17 00:00:00 2001
From: vincentlu <t1341870251@gmail.com>
Date: 星期一, 19 一月 2026 11:30:25 +0800
Subject: [PATCH] #
---
zy-acs-flow/src/page/login0/index.jsx | 394 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
zy-acs-flow/src/page/login/index.jsx | 2
2 files changed, 395 insertions(+), 1 deletions(-)
diff --git a/zy-acs-flow/src/page/login/index.jsx b/zy-acs-flow/src/page/login/index.jsx
index 3a9c3c1..dbadebb 100644
--- a/zy-acs-flow/src/page/login/index.jsx
+++ b/zy-acs-flow/src/page/login/index.jsx
@@ -67,7 +67,7 @@
</video>
<Card sx={{
- width: 400,
+ width: 380,
marginTop: formPosition === 'middle' ? '6em' : 0,
zIndex: 1,
bgcolor: DEFAULT_THEME_MODE === 'light' ? '#fff' : '#121212',
diff --git a/zy-acs-flow/src/page/login0/index.jsx b/zy-acs-flow/src/page/login0/index.jsx
new file mode 100644
index 0000000..064992a
--- /dev/null
+++ b/zy-acs-flow/src/page/login0/index.jsx
@@ -0,0 +1,394 @@
+import React, { useState, useEffect, useMemo, useRef } from "react";
+import { Box, Typography } from "@mui/material";
+import { useLogin, useNotify } from "react-admin";
+import { useLocation } from "react-router-dom";
+import { SPA_NAME, SPA_VERSION } from "@/config/setting";
+
+const asciiArt = `
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@@@@@@@ RRRRR CCCCC SSSSS @@@@@@@
+@@@@@@@ RR RR CC CC SS S @@@@@@@
+@@@@@@@ RRRRR CC SSSSS @@@@@@@
+@@@@@@@ RR RR CC CC SS @@@@@@@
+@@@@@@@ RR RR CCCCC SSSSS @@@@@@@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+`;
+
+const promptTexts = {
+ username: "Enter login ID: ",
+ password: "Enter password: ",
+};
+
+const LABEL_COLOR = "#ff4cb2";
+const VALUE_COLOR = "#7dff82";
+const PROMPT_COLOR = "#46e46e";
+
+const Login0 = () => {
+ const login = useLogin();
+ const notify = useNotify();
+ const location = useLocation();
+ const inputRef = useRef(null);
+ const bootTime = useRef(Date.now());
+
+ const [phase, setPhase] = useState("boot");
+ const [inputValue, setInputValue] = useState("");
+ const [history, setHistory] = useState([]);
+ const [username, setUsername] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [displayedPrompt, setDisplayedPrompt] = useState("");
+ const [uptime, setUptime] = useState("0m 00s");
+
+ const host = typeof window !== "undefined" ? window.location.host : "localhost";
+ const platform = typeof navigator !== "undefined" ? navigator.platform : "RCS OS";
+ const shellName = `${(SPA_NAME || "rcs").toLowerCase()}-shell`;
+
+ const bootScript = useMemo(() => {
+ const scanTime = new Date().toLocaleString();
+ return [
+ { text: `${host}@mystery-visitor:~$ neofetch`, tone: "command", delay: 300 },
+ { text: "RCS Flow Console ready.", tone: "system", delay: 700 },
+ { text: `Scan timestamp : ${scanTime}`, tone: "system", delay: 700 },
+ { text: "Awaiting credentials ...", tone: "accent", delay: 800 },
+ ];
+ }, [host]);
+
+ useEffect(() => {
+ const id = setInterval(() => {
+ const seconds = Math.floor((Date.now() - bootTime.current) / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const remain = seconds % 60;
+ setUptime(`${minutes}m ${remain.toString().padStart(2, "0")}s`);
+ }, 1000);
+ return () => clearInterval(id);
+ }, []);
+
+ useEffect(() => {
+ if (phase !== "boot") {
+ return;
+ }
+ const timers = [];
+ let totalDelay = 0;
+ bootScript.forEach((line, index) => {
+ totalDelay += line.delay ?? 700;
+ const timerId = setTimeout(() => {
+ setHistory((prev) => [...prev, { text: line.text, tone: line.tone }]);
+ if (index === bootScript.length - 1) {
+ const doneId = setTimeout(() => setPhase("username"), 400);
+ timers.push(doneId);
+ }
+ }, totalDelay);
+ timers.push(timerId);
+ });
+ return () => {
+ timers.forEach((timer) => clearTimeout(timer));
+ };
+ }, [phase, bootScript]);
+
+ const systemInfo = useMemo(() => [
+ { label: "Host", value: host || "localhost" },
+ { label: "OS", value: platform },
+ { label: "Shell", value: shellName },
+ { label: "Version", value: SPA_VERSION || "1.0" },
+ { label: "Theme", value: "homebrew console" },
+ { label: "Uptime", value: uptime },
+ { label: "Instruction", value: "Submit ID then password" },
+ ], [host, platform, shellName, uptime]);
+
+ useEffect(() => {
+ if (phase !== "username" && phase !== "password") {
+ setDisplayedPrompt("");
+ return;
+ }
+ const text = promptTexts[phase];
+ setDisplayedPrompt("");
+ let index = 0;
+ const timer = setInterval(() => {
+ setDisplayedPrompt((prev) => prev + text[index]);
+ index += 1;
+ if (index >= text.length) {
+ clearInterval(timer);
+ }
+ }, 35);
+ return () => clearInterval(timer);
+ }, [phase]);
+
+ useEffect(() => {
+ if ((phase === "username" || phase === "password") && !loading) {
+ inputRef.current?.focus();
+ }
+ }, [phase, loading]);
+
+ const pushHistory = (text, tone = "default") => {
+ setHistory((prev) => [...prev, { text, tone }]);
+ };
+
+ const resetToUsername = (message) => {
+ if (message) {
+ pushHistory(message, "warning");
+ }
+ setPhase("username");
+ setUsername("");
+ setInputValue("");
+ };
+
+ const handleLogin = (payload) => {
+ setLoading(true);
+ setPhase("submitting");
+ pushHistory(">> verifying credentials ...", "system");
+
+ login(
+ payload,
+ location.state ? location.state.nextPathname : "/"
+ )
+ .then(() => {
+ pushHistory(">> access granted. redirecting ...", "accent");
+ })
+ .catch((res) => {
+ const { code, msg } = res || {};
+ const message = msg || "Unknown error";
+ if (code === 10003 || code === 10004) {
+ resetToUsername(`Error(${code}): ${message}`);
+ } else if (code === 10001) {
+ setPhase("password");
+ setInputValue("");
+ pushHistory(`Error(${code}): ${message}`, "error");
+ } else {
+ pushHistory(`Error: ${message}`, "error");
+ setPhase("password");
+ setInputValue("");
+ }
+ notify(message, { type: "error", messageArgs: { _: message } });
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ };
+
+ const handleAdvance = () => {
+ if (loading) {
+ return;
+ }
+ const trimmed = inputValue.trim();
+ if (!trimmed) {
+ pushHistory("Warning: input cannot be empty.", "warning");
+ return;
+ }
+
+ if (phase === "username") {
+ setUsername(trimmed);
+ pushHistory(`${promptTexts.username}${trimmed}`, "command");
+ setPhase("password");
+ setInputValue("");
+ return;
+ }
+
+ if (phase === "password") {
+ pushHistory(`${promptTexts.password}${"*".repeat(trimmed.length)}`, "command");
+ setInputValue("");
+ handleLogin({ username, password: trimmed });
+ }
+ };
+
+ const renderPrompt = phase === "username" || phase === "password";
+
+ const topLine = `${host}@mystery-visitor:~$ neofetch`;
+ const bottomLine = `${host}@mystery-visitor:~$ type "help" if you are lost ...`;
+ const brandTop = (SPA_NAME || "RCS").toUpperCase();
+ const brandBottom = "FLOW";
+
+ return (
+ <Box
+ sx={{
+ minHeight: "100vh",
+ backgroundColor: "#030203",
+ color: "#aee6b8",
+ fontFamily: `'OCR A Std', 'Share Tech Mono', 'Courier New', monospace`,
+ fontSize: { xs: 11, md: 13 },
+ letterSpacing: "0.04em",
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "space-between",
+ padding: { xs: 2, md: 4 },
+ gap: 3,
+ }}
+ >
+ <Typography sx={{ color: PROMPT_COLOR, width: "100%", textAlign: "left" }}>
+ {topLine}
+ </Typography>
+
+ <Box
+ sx={{
+ width: "100%",
+ display: "flex",
+ flexDirection: { xs: "column", md: "row" },
+ gap: { xs: 3, md: 8 },
+ }}
+ >
+ <Box sx={{ flex: 1, minWidth: 0 }}>
+ <Typography
+ component="pre"
+ sx={{
+ color: "#ff3f9c",
+ m: 0,
+ whiteSpace: "pre",
+ fontSize: { xs: 10, md: 14 },
+ lineHeight: 1.05,
+ }}
+ >
+ {asciiArt}
+ </Typography>
+ </Box>
+
+ <Box
+ sx={{
+ flex: 1,
+ display: "flex",
+ flexDirection: "column",
+ gap: 2,
+ minWidth: 0,
+ }}
+ >
+ <Box>
+ <Typography
+ sx={{
+ fontSize: { xs: 26, md: 44 },
+ letterSpacing: { xs: "0.4em", md: "0.6em" },
+ color: "#6ef68c",
+ textTransform: "uppercase",
+ textShadow: "0 0 6px rgba(110,246,140,0.7)",
+ }}
+ >
+ {brandTop}
+ </Typography>
+ <Typography
+ sx={{
+ fontSize: { xs: 24, md: 40 },
+ letterSpacing: { xs: "0.35em", md: "0.55em" },
+ color: "#6ef68c",
+ textTransform: "uppercase",
+ textShadow: "0 0 6px rgba(110,246,140,0.7)",
+ }}
+ >
+ {brandBottom}
+ </Typography>
+ </Box>
+
+ <Box
+ component="ul"
+ sx={{
+ listStyle: "none",
+ m: 0,
+ p: 0,
+ display: "flex",
+ flexDirection: "column",
+ gap: 0.3,
+ }}
+ >
+ {systemInfo.map((item) => (
+ <li key={item.label} style={{ display: "flex", gap: "12px" }}>
+ <Typography component="span" sx={{ minWidth: 120, color: LABEL_COLOR }}>
+ {item.label}:
+ </Typography>
+ <Typography component="span" sx={{ color: VALUE_COLOR }}>
+ {item.value}
+ </Typography>
+ </li>
+ ))}
+ </Box>
+ </Box>
+ </Box>
+
+ <Box
+ component="form"
+ onSubmit={(event) => {
+ event.preventDefault();
+ handleAdvance();
+ }}
+ sx={{
+ width: "100%",
+ display: "flex",
+ flexDirection: "column",
+ gap: 1,
+ mt: { xs: 2, md: 3 },
+ maxWidth: 960,
+ }}
+ >
+ <Typography sx={{ color: "#f1f1f1", fontSize: { xs: 12, md: 13 } }}>
+ ACCESS LOG
+ </Typography>
+ <Box
+ sx={{
+ minHeight: 160,
+ maxHeight: 220,
+ overflowY: "auto",
+ color: "#c5e6c5",
+ display: "flex",
+ flexDirection: "column",
+ gap: 0.2,
+ fontSize: { xs: 11, md: 13 },
+ }}
+ >
+ {history.map((line, index) => (
+ <Typography
+ key={`${line.tone}-${index}`}
+ sx={{
+ color:
+ line.tone === "accent"
+ ? "#73ee73"
+ : line.tone === "system"
+ ? "#58c8ff"
+ : line.tone === "command"
+ ? "#ffffff"
+ : line.tone === "warning"
+ ? "#e6cd67"
+ : line.tone === "error"
+ ? "#ff7a7a"
+ : "#b8d9b8",
+ }}
+ >
+ {line.text}
+ </Typography>
+ ))}
+ </Box>
+
+ {renderPrompt && (
+ <Box sx={{ display: "flex", alignItems: "center", color: "#fff" }}>
+ <Typography component="span" sx={{ color: PROMPT_COLOR }}>
+ {displayedPrompt}
+ </Typography>
+ <Box
+ component="input"
+ ref={inputRef}
+ value={inputValue}
+ onChange={(event) => setInputValue(event.target.value)}
+ disabled={loading}
+ type={phase === "password" ? "password" : "text"}
+ autoComplete="off"
+ sx={{
+ flex: 1,
+ border: "none",
+ outline: "none",
+ background: "transparent",
+ color: "#ffffff",
+ fontFamily: `'OCR A Std', 'Courier New', monospace`,
+ fontSize: { xs: 12, md: 13 },
+ ml: 1,
+ }}
+ />
+ </Box>
+ )}
+
+ {phase === "submitting" && (
+ <Typography sx={{ color: "#73ee73" }}> CONNECTING ...</Typography>
+ )}
+ </Box>
+
+ <Typography sx={{ color: PROMPT_COLOR, width: "100%", textAlign: "left" }}>
+ {bottomLine}
+ </Typography>
+ </Box>
+ );
+};
+
+export default Login0;
--
Gitblit v1.9.1