From d0051a5ecf6a755918099138253da7ccecced5ba Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sun, 7 Jun 2026 13:05:26 +0800 Subject: [PATCH] feat(home): dismissible 'set recovery password' reminder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to deferring the forced set-password gate: now that new SSO users go straight to the magic moment, surface a gentle, dismissible banner on the env home when they have no local credential yet (useAuth().hasLocalPassword() === false) — links to /set-password, remembers dismissal in localStorage. Preserves instance self-sufficiency without walling off the first session. zh + en. Co-Authored-By: Claude Opus 4.8 --- .../app-shell/src/console/home/HomePage.tsx | 59 ++++++++++++++++++- packages/i18n/src/locales/en.ts | 5 ++ packages/i18n/src/locales/zh.ts | 5 ++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/packages/app-shell/src/console/home/HomePage.tsx b/packages/app-shell/src/console/home/HomePage.tsx index b248bbf3f..f403b9aa1 100644 --- a/packages/app-shell/src/console/home/HomePage.tsx +++ b/packages/app-shell/src/console/home/HomePage.tsx @@ -16,7 +16,7 @@ * @module */ -import { useMemo, type ComponentType } from 'react'; +import { useMemo, useState, useEffect, type ComponentType } from 'react'; import { useNavigate } from 'react-router-dom'; import { useMetadata } from '../../providers/MetadataProvider'; import { useRecentItems } from '../../hooks/useRecentItems'; @@ -27,7 +27,7 @@ import { AppCard } from './AppCard'; import { RecentApps } from './RecentApps'; import { StarredApps } from './StarredApps'; import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components'; -import { Plus, Settings, Sparkles, Star, Clock, ArrowDown, Store, LayoutGrid } from 'lucide-react'; +import { Plus, Settings, Sparkles, Star, Clock, ArrowDown, Store, LayoutGrid, ShieldAlert, X } from 'lucide-react'; function pickGreetingKey(hour: number): string { if (hour < 5) return 'home.greetingNight'; @@ -102,6 +102,54 @@ function StatPill({ ); } +/** + * Dismissible nudge to set a local recovery password — shown when the user + * signed in via SSO and has no local credential yet. We no longer force this + * before the first session (it walled off the magic moment); this gentle, + * one-time reminder preserves instance self-sufficiency without the friction. + */ +function RecoveryPasswordReminder({ t }: { t: (key: string, opts?: any) => string }) { + const navigate = useNavigate(); + const { hasLocalPassword } = useAuth(); + const [show, setShow] = useState(false); + useEffect(() => { + if (typeof localStorage !== 'undefined' && localStorage.getItem('os:recovery-pw-dismissed') === '1') return; + let cancelled = false; + Promise.resolve(hasLocalPassword?.()) + .then((has) => { if (!cancelled && has === false) setShow(true); }) + .catch(() => { /* unknown → don't nag */ }); + return () => { cancelled = true; }; + }, [hasLocalPassword]); + const dismiss = () => { + try { localStorage.setItem('os:recovery-pw-dismissed', '1'); } catch { /* ignore */ } + setShow(false); + }; + if (!show) return null; + return ( +
+
+
+ +

+ {t('home.recoveryReminder.message', { defaultValue: 'Set a recovery password so you can still sign in if single sign-on is ever unavailable.' })} +

+ + +
+
+
+ ); +} + export function HomePage() { const navigate = useNavigate(); const { t } = useObjectTranslation(); @@ -134,7 +182,9 @@ export function HomePage() { if (activeApps.length === 0) { return ( -
+
+ +
{t('home.welcome', { defaultValue: 'Welcome to ObjectUI' })} @@ -164,6 +214,7 @@ export function HomePage() {
+
); } @@ -177,6 +228,8 @@ export function HomePage() { the gradient display name in the hero. */} + + {/* Hero */}
diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index 5bd747709..aa6a01749 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -1455,6 +1455,11 @@ const en = { welcome: 'Build your business system with AI', welcomeDescription: 'Describe your business in one sentence — AI generates the objects, screens, APIs and agent tools. Or start from scratch.', buildWithAI: 'Build with AI', + recoveryReminder: { + message: 'Set a recovery password so you can still sign in if single sign-on is ever unavailable.', + cta: 'Set password', + dismiss: 'Dismiss', + }, createFirstApp: 'Create app manually', systemSettings: 'System Settings', browseMarketplace: 'Browse App Marketplace', diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts index 0ce2babcb..7e60f6520 100644 --- a/packages/i18n/src/locales/zh.ts +++ b/packages/i18n/src/locales/zh.ts @@ -1468,6 +1468,11 @@ const zh = { welcome: '用 AI 搭建你的业务系统', welcomeDescription: '用一句话描述你的业务,AI 帮你生成对象、界面、API 和 agent 工具。也可以手动从零开始。', buildWithAI: '用 AI 搭建', + recoveryReminder: { + message: '建议设置一个备用密码,这样即使单点登录不可用,你仍能直接登录此环境。', + cta: '设置密码', + dismiss: '关闭', + }, createFirstApp: '手动创建应用', systemSettings: '系统设置', browseMarketplace: '浏览应用市场',