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