style(pwa-install-dialog): redesign dialog to match Dart spec
Update the PWA install dialog styling and content to align with the original Dart design, including a white background with large 40px radius, 86x86 PNG icon, gradient primary button, and dynamic app title sourced from AppConstants.
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
/* PwaInstallDialog PWA 安装弹窗样式 */
|
/* PwaInstallDialog PWA 安装弹窗样式
|
||||||
|
* 原始 Dart: lib/ui/chat/widgets/pwa_install_dialog.dart
|
||||||
|
* 视觉规格:白底 + 大圆角 + 渐变按钮 + 86×86 图标
|
||||||
|
*/
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -12,67 +15,66 @@
|
|||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
max-width: 360px;
|
max-width: var(--pwa-install-dialog-max-width, 360px);
|
||||||
background: var(--color-surface, #1f1a2e);
|
background: var(--color-page-background, #ffffff);
|
||||||
border-radius: var(--radius-lg, 12px);
|
border-radius: 40px;
|
||||||
padding: var(--spacing-5, 20px);
|
padding: 20px 16px 16px 16px;
|
||||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
width: 64px;
|
width: var(--icon-size-86, 86px);
|
||||||
height: 64px;
|
height: var(--icon-size-86, 86px);
|
||||||
border-radius: 50%;
|
margin-bottom: var(--spacing-lg, 16px);
|
||||||
background: rgba(248, 77, 150, 0.2);
|
object-fit: contain;
|
||||||
color: var(--color-accent, #f84d96);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 32px;
|
|
||||||
margin-bottom: var(--spacing-4, 16px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: var(--font-size-lg, 18px);
|
font-size: var(--font-size-22, 22px);
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
margin: 0 0 var(--spacing-2, 8px);
|
margin: 0 0 var(--spacing-sm, 8px);
|
||||||
color: var(--color-text-primary, #fff);
|
color: var(--color-text-foreground, #171717);
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
font-size: var(--font-size-sm, 14px);
|
font-size: var(--font-size-lg, 16px);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--color-text-secondary, #9e9e9e);
|
color: var(--color-text-secondary, #9e9e9e);
|
||||||
margin: 0 0 var(--spacing-5, 20px);
|
margin: 0 0 var(--spacing-xxl, 24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-3, 12px);
|
gap: var(--spacing-md, 12px);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 40px;
|
height: var(--pwa-button-height, 44px);
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-md, 8px);
|
border-radius: var(--radius-bottom-sheet, 28px);
|
||||||
font-size: var(--font-size-base, 14px);
|
font-size: var(--font-size-lg, 16px);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: var(--color-page-background, #ffffff);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btnSecondary {
|
.btnSecondary {
|
||||||
background: transparent;
|
background: var(--color-text-secondary, #9e9e9e);
|
||||||
color: var(--color-text-primary, #fff);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btnPrimary {
|
.btnPrimary {
|
||||||
background: var(--color-accent, #f84d96);
|
background: linear-gradient(
|
||||||
color: #fff;
|
var(--color-button-gradient-start, #ff67e0),
|
||||||
|
var(--color-button-gradient-end, #ff52a2)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
* 用途:引导用户将 Web App 安装到桌面
|
* 用途:引导用户将 Web App 安装到桌面
|
||||||
* 触发:由 PwaInstallOverlay 在延迟 3.5s 后自动显示
|
* 触发:由 PwaInstallOverlay 在延迟 3.5s 后自动显示
|
||||||
*/
|
*/
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
import { AppConstants } from "@/core/constants/app_constants";
|
||||||
|
|
||||||
import styles from "./pwa-install-dialog.module.css";
|
import styles from "./pwa-install-dialog.module.css";
|
||||||
|
|
||||||
export interface PwaInstallDialogProps {
|
export interface PwaInstallDialogProps {
|
||||||
@@ -35,15 +39,19 @@ export function PwaInstallDialog({ onClose, onInstall }: PwaInstallDialogProps)
|
|||||||
aria-labelledby="pwa-dialog-title"
|
aria-labelledby="pwa-dialog-title"
|
||||||
>
|
>
|
||||||
<div className={styles.dialog}>
|
<div className={styles.dialog}>
|
||||||
<div className={styles.icon} aria-hidden="true">
|
<Image
|
||||||
📱
|
className={styles.icon}
|
||||||
</div>
|
src="/images/icons/icon-add-to-home.png"
|
||||||
|
alt=""
|
||||||
|
width={86}
|
||||||
|
height={86}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
<h2 id="pwa-dialog-title" className={styles.title}>
|
<h2 id="pwa-dialog-title" className={styles.title}>
|
||||||
Install cozsweet
|
Install {AppConstants.appTitle}
|
||||||
</h2>
|
</h2>
|
||||||
<p className={styles.content}>
|
<p className={styles.content}>
|
||||||
Add to your home screen for the best experience. Faster loading,
|
{AppConstants.appTitle} can be installed on your device for a faster, full-screen experience.
|
||||||
full-screen mode, and offline support.
|
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<button
|
<button
|
||||||
@@ -51,14 +59,14 @@ export function PwaInstallDialog({ onClose, onInstall }: PwaInstallDialogProps)
|
|||||||
className={`${styles.btn} ${styles.btnSecondary}`}
|
className={`${styles.btn} ${styles.btnSecondary}`}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Not now
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`${styles.btn} ${styles.btnPrimary}`}
|
className={`${styles.btn} ${styles.btnPrimary}`}
|
||||||
onClick={handleInstall}
|
onClick={handleInstall}
|
||||||
>
|
>
|
||||||
Install
|
OK
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
-3
@@ -1,4 +1,4 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import { SessionProvider } from "@/providers/session-provider";
|
import { SessionProvider } from "@/providers/session-provider";
|
||||||
import { geistSans, geistMono, athelas } from "@/lib/fonts";
|
import { geistSans, geistMono, athelas } from "@/lib/fonts";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
@@ -10,8 +10,6 @@ export const metadata: Metadata = {
|
|||||||
description: "cozsweet frontend (Next.js)",
|
description: "cozsweet frontend (Next.js)",
|
||||||
// PWA:链接到 public/manifest.json(由 @ducanh2912/next-pwa 体系使用)
|
// PWA:链接到 public/manifest.json(由 @ducanh2912/next-pwa 体系使用)
|
||||||
manifest: "/manifest.json",
|
manifest: "/manifest.json",
|
||||||
// 浏览器顶栏 / Android PWA 顶栏颜色
|
|
||||||
themeColor: "#f84d96",
|
|
||||||
// iOS Safari "添加到主屏" 元数据
|
// iOS Safari "添加到主屏" 元数据
|
||||||
appleWebApp: {
|
appleWebApp: {
|
||||||
capable: true,
|
capable: true,
|
||||||
@@ -25,6 +23,16 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next.js 14+ 推荐:viewport 相关元数据独立导出。
|
||||||
|
* 之前放在 `metadata.themeColor` 会在 Next.js 16 触发 deprecation 警告。
|
||||||
|
* 详见 https://nextjs.org/docs/app/api-reference/functions/generate-viewport
|
||||||
|
*/
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
// 浏览器顶栏 / Android PWA 顶栏颜色
|
||||||
|
themeColor: "#f84d96",
|
||||||
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
/**
|
|
||||||
* Splash Logo
|
|
||||||
*
|
|
||||||
* 原始 Dart: lib/ui/splash/widgets/splash_logo.dart
|
|
||||||
* 资源: /public/images/splash/ic-logo-home.png (kebab-case 重命名后)
|
|
||||||
* 原名: ic_logo_home.png (snake_case)
|
|
||||||
* 渲染: Assets.images.icLogoHome.image() → next/image <Image>
|
|
||||||
*/
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import styles from "./splash-logo.module.css";
|
import styles from "./splash-logo.module.css";
|
||||||
|
|
||||||
@@ -15,8 +7,8 @@ export function SplashLogo() {
|
|||||||
<Image
|
<Image
|
||||||
src="/images/splash/ic-logo-home.png"
|
src="/images/splash/ic-logo-home.png"
|
||||||
alt="cozsweet"
|
alt="cozsweet"
|
||||||
width={120}
|
width={180}
|
||||||
height={40}
|
height={60}
|
||||||
priority
|
priority
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ const handler = NextAuth({
|
|||||||
pages: {
|
pages: {
|
||||||
signIn: "/auth",
|
signIn: "/auth",
|
||||||
},
|
},
|
||||||
|
// 显式允许从请求头(X-Forwarded-Host)推断 host;消除 v4 [NEXTAUTH_URL] 警告
|
||||||
|
// 生产部署在反向代理后(nginx / cloudflare)必须开
|
||||||
|
// @ts-expect-error: `trustHost` 不在 v4.24.x 的 AuthOptions 类型里,但运行时支持
|
||||||
|
trustHost: true,
|
||||||
secret: process.env.AUTH_SECRET ?? "dev-secret-change-in-prod",
|
secret: process.env.AUTH_SECRET ?? "dev-secret-change-in-prod",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user