refactor: relocate components to app directory structure
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
.bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle at 80% 20%, rgba(248, 77, 150, 0.25), transparent 60%),
|
||||
linear-gradient(180deg, #1a0f2a 0%, #0d0b14 100%);
|
||||
z-index: 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
"use client";
|
||||
/**
|
||||
* Splash 背景
|
||||
*
|
||||
* 原始 Dart: lib/ui/splash/widgets/splash_background.dart
|
||||
* 使用 `Assets.images.picBgHome.image(fit: BoxFit.cover)` 渲染全屏背景图。
|
||||
*
|
||||
* 当前实现:纯色 + CSS 渐变占位(待 `public/splash/bg.png` 资源到位后可切换为 `<Image>`)。
|
||||
*/
|
||||
import styles from "./splash-background.module.css";
|
||||
|
||||
export function SplashBackground() {
|
||||
return <div className={styles.bg} aria-hidden="true" />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
/**
|
||||
* Splash 底部按钮组(Skip + Facebook 登录)
|
||||
*
|
||||
* 原始 Dart: lib/ui/splash/widgets/splash_button.dart
|
||||
*/
|
||||
import { useAuthState } from "@/contexts/auth/auth-context";
|
||||
|
||||
import { AuthSocialButtons } from "@/app/auth/components/auth-social-buttons";
|
||||
import { AuthPrimaryButton } from "@/app/auth/components/auth-primary-button";
|
||||
import { AuthDivider } from "@/app/auth/components/auth-divider";
|
||||
import { useAuthDispatch } from "@/contexts/auth/auth-context";
|
||||
import styles from "./splash-button.module.css";
|
||||
|
||||
export interface SplashButtonProps {
|
||||
onSkip: () => void;
|
||||
}
|
||||
|
||||
export function SplashButton({ onSkip }: SplashButtonProps) {
|
||||
const state = useAuthState();
|
||||
const dispatch = useAuthDispatch();
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<AuthSocialButtons
|
||||
loadingProvider={state.isLoading ? "facebook" : null}
|
||||
onFacebook={() => dispatch({ type: "AuthFacebookLoginSubmitted" })}
|
||||
onGoogle={() => dispatch({ type: "AuthGoogleLoginSubmitted" })}
|
||||
/>
|
||||
<AuthDivider label="or" />
|
||||
<AuthPrimaryButton onClick={onSkip} variant="primary">
|
||||
Skip — Continue as Guest
|
||||
</AuthPrimaryButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
color: var(--color-text-primary);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-family: var(--font-athelas);
|
||||
font-size: var(--font-size-26);
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-md);
|
||||
line-height: 1.5;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
/**
|
||||
* Splash 内容文案
|
||||
*
|
||||
* 原始 Dart: lib/ui/splash/widgets/splash_content.dart
|
||||
*/
|
||||
import styles from "./splash-content.module.css";
|
||||
|
||||
export function SplashContent() {
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<h1 className={styles.title}>Chat with Elio, anytime, anywhere</h1>
|
||||
<p className={styles.subtitle}>
|
||||
A safe, private space to talk, share, and unwind — your AI companion who's always there.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
/**
|
||||
* Splash 加载状态
|
||||
*
|
||||
* 原始 Dart: lib/ui/splash/widgets/splash_loading.dart
|
||||
*
|
||||
* 包装 `<LoadingIndicator>`,splash 阶段可能用于等待 auth 检查。
|
||||
*/
|
||||
import { LoadingIndicator } from "@/app/_components/core/loading-indicator";
|
||||
|
||||
export function SplashLoading() {
|
||||
return <LoadingIndicator size={48} color="var(--color-accent)" />;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
color: var(--color-text-primary);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-family: var(--font-athelas);
|
||||
font-size: var(--font-size-title-x-large);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
/**
|
||||
* Splash Logo
|
||||
*
|
||||
* 原始 Dart: lib/ui/splash/widgets/splash_logo.dart
|
||||
*
|
||||
* 当前实现:纯文字 logo("cozsweet" + tagline)。
|
||||
* 资源:`Assets.images.icLogoHome`(待替换为 `<Image src="/splash/logo.svg" />`)。
|
||||
*/
|
||||
import styles from "./splash-logo.module.css";
|
||||
|
||||
export function SplashLogo() {
|
||||
return (
|
||||
<div className={styles.logo}>
|
||||
<span className={styles.brand}>cozsweet</span>
|
||||
<span className={styles.tagline}>Your exclusive AI boyfriend</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
border-radius: var(--radius-full);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 30%;
|
||||
border-radius: var(--radius-full);
|
||||
background: linear-gradient(90deg, var(--color-accent), var(--color-facebook-button-gradient-end));
|
||||
animation: progress 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes progress {
|
||||
0% {
|
||||
left: -30%;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
/**
|
||||
* Splash 进度条
|
||||
*
|
||||
* 原始 Dart: lib/ui/splash/widgets/splash_progress.dart
|
||||
*
|
||||
* 当前实现:简单 CSS 动画进度条,0 → 100% 1.5s。
|
||||
*/
|
||||
import styles from "./splash-progress.module.css";
|
||||
|
||||
export function SplashProgress() {
|
||||
return (
|
||||
<div className={styles.wrapper} role="progressbar" aria-valuemin={0} aria-valuemax={100}>
|
||||
<div className={styles.bar} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-height: 100dvh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gradientOverlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
to top right,
|
||||
var(--color-accent),
|
||||
transparent 60%
|
||||
);
|
||||
opacity: 0.4;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: var(--spacing-26) var(--spacing-26) var(--spacing-md);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.buttonArea {
|
||||
margin-top: var(--spacing-26);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin: var(--spacing-xxxl) 0 0 0;
|
||||
font-size: var(--font-size-md);
|
||||
line-height: 1.5;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { useAuthState } from "@/contexts/auth/auth-context";
|
||||
import { useChatDispatch } from "@/contexts/chat/chat-context";
|
||||
import { useUserDispatch } from "@/contexts/user/user-context";
|
||||
import { useAuthGate } from "@/lib/auth/use-auth-gate";
|
||||
import { ROUTES } from "@/lib/routes";
|
||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||
|
||||
import { SplashBackground } from "./splash-background";
|
||||
import { SplashLogo } from "./splash-logo";
|
||||
import { SplashContent } from "./splash-content";
|
||||
import { SplashButton } from "./splash-button";
|
||||
import styles from "./splash-screen.module.css";
|
||||
|
||||
export function SplashScreen() {
|
||||
const router = useRouter();
|
||||
const { isAuthed } = useAuthGate();
|
||||
const state = useAuthState();
|
||||
const wasSuccess = useRef(false);
|
||||
const chatDispatch = useChatDispatch();
|
||||
const userDispatch = useUserDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthed) router.replace(ROUTES.chat);
|
||||
}, [isAuthed, router]);
|
||||
|
||||
// 登录成功跳 /chat
|
||||
useEffect(() => {
|
||||
if (state.isSuccess && !wasSuccess.current) {
|
||||
wasSuccess.current = true;
|
||||
chatDispatch({ type: "ChatAuthStatusChanged" });
|
||||
userDispatch({ type: "UserInit" });
|
||||
router.replace(ROUTES.chat);
|
||||
}
|
||||
}, [state.isSuccess, chatDispatch, userDispatch, router]);
|
||||
|
||||
return (
|
||||
<MobileShell background="var(--color-sidebar-background)">
|
||||
<div className={styles.wrapper}>
|
||||
<SplashBackground />
|
||||
<div className={styles.gradientOverlay} aria-hidden="true" />
|
||||
<div className={styles.content}>
|
||||
<SplashLogo />
|
||||
<div className={styles.spacer} />
|
||||
<SplashContent />
|
||||
<div className={styles.buttonArea}>
|
||||
<SplashButton onSkip={() => router.push(ROUTES.chat)} />
|
||||
</div>
|
||||
<p className={styles.bottom}>
|
||||
Elio Silvestri, Your exclusive AI boyfriend
|
||||
<br />
|
||||
24/7 online | Chat | Companion | Heal | Sweet moments
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</MobileShell>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { SplashScreen } from "@/components/splash/splash-screen";
|
||||
import { SplashScreen } from "@/app/splash/components/splash-screen";
|
||||
|
||||
export default function SplashPage() {
|
||||
return <SplashScreen />;
|
||||
|
||||
Reference in New Issue
Block a user