Roll-up of tonight's desktop fixes shipped as 0.1.1: - PermissionDialog: cap diff/command preview at max-h-360 so the allow/deny buttons stay above the fold on huge writes (user reported scrolling 900 lines to find the buttons). - SessionTaskBar + cliTaskStore: hover row shows ✓ manual-close button for tasks the agent forgot to mark completed. - AskUserQuestion: honor schema's `multiSelect: true` — toggle membership across options, switch round → square indicator, show "可多选" hint, join answers with comma. - HeicodeLoginPage: removed dead `'official'` filter that blocked the typescript build (legacy provider id no longer in the union). - Settings About: removed the third-party social-media block and unused openUrl helper. - i18n: replaced misleading "GitHub Releases" wording with "Heicode 官方更新源" / "Heicode update source" — actual channel is Azure Blob (msi/updater/latest.json) per tauri.conf.json. - release-desktop.mjs: az invocations now run with shell:true so the Windows `az.cmd` resolves; also uploads the manifest to blob (primary endpoint) as the script finishes. - tauri.conf.json + package.json + Cargo.toml: version bumped to 0.1.1. - website/public/updater/latest.json: now reflects 0.1.1 + new signed NSIS URL (mirrored to blob by the release script). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
167 lines
6.5 KiB
TypeScript
167 lines
6.5 KiB
TypeScript
// desktop/src/components/login/HeicodeLoginPage.tsx
|
|
//
|
|
// Production-grade login surface. Layout follows
|
|
// docs/product-package/11-product-prototype-wireframes.md §1
|
|
// (brand wordmark + tagline + single sign-in card) but the visual
|
|
// system was tightened up:
|
|
//
|
|
// - 36px chrome strip with drag region + window controls
|
|
// - Layered gradient backdrop (violet/blue glow at top, vignette at
|
|
// bottom) so the login surface feels like a hero, not a form
|
|
// - Centered logo image + wordmark above tagline; both pull the
|
|
// same gradient as --gradient-brand-wordmark so the login screen
|
|
// matches the app icon you see in the title bar / taskbar / dock
|
|
// - Single CTA card on a generous max-w-sm canvas
|
|
// - Footer copy lives at the very bottom, not floating in the middle
|
|
//
|
|
// Visual references: Linear / Vercel / Raycast desktop login screens.
|
|
|
|
import { useEffect } from 'react'
|
|
import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore'
|
|
import { ProviderLoginCard } from './ProviderLoginCard'
|
|
import { WindowControls } from '../layout/WindowControls'
|
|
import { useTranslation } from '../../i18n'
|
|
|
|
export function HeicodeLoginPage() {
|
|
const t = useTranslation()
|
|
const {
|
|
providers,
|
|
hasFetched,
|
|
isLoading,
|
|
error,
|
|
fetch: fetchAuth,
|
|
stopOAuthPolling,
|
|
} = useHeicodeAuthStore()
|
|
|
|
useEffect(() => {
|
|
if (!hasFetched) {
|
|
void fetchAuth()
|
|
}
|
|
return () => {
|
|
stopOAuthPolling()
|
|
}
|
|
}, [hasFetched, fetchAuth, stopOAuthPolling])
|
|
|
|
return (
|
|
<div
|
|
className="relative flex h-screen w-screen flex-col overflow-hidden bg-[var(--color-background)]"
|
|
data-testid="heicode-login-page"
|
|
>
|
|
{/* ─── Chrome strip ────────────────────────────────────── */}
|
|
<div
|
|
className="relative z-20 flex h-9 w-full items-center justify-between border-b border-[var(--color-border-separator)] bg-[var(--color-surface)]/70 backdrop-blur-md"
|
|
data-tauri-drag-region
|
|
>
|
|
<div className="flex items-center gap-2 px-4" data-tauri-drag-region>
|
|
<span
|
|
className="bg-clip-text text-[10px] font-bold uppercase tracking-[0.22em] text-transparent"
|
|
style={{ backgroundImage: 'var(--gradient-brand-wordmark)' }}
|
|
>
|
|
Heicode
|
|
</span>
|
|
</div>
|
|
<WindowControls />
|
|
</div>
|
|
|
|
{/* ─── Layered backdrop ────────────────────────────────── */}
|
|
{/* Top violet halo */}
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-x-0 top-0 z-0 h-[60vh]"
|
|
style={{
|
|
backgroundImage:
|
|
'radial-gradient(ellipse 70% 50% at 50% 0%, rgba(123,107,227,0.18), transparent 60%)',
|
|
}}
|
|
/>
|
|
{/* Lower blue glow */}
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-x-0 bottom-0 z-0 h-[40vh]"
|
|
style={{
|
|
backgroundImage:
|
|
'radial-gradient(ellipse 60% 60% at 50% 100%, rgba(107,124,224,0.10), transparent 60%)',
|
|
}}
|
|
/>
|
|
{/* Subtle grain — adds the "real product" tactility */}
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-0 z-0 opacity-[0.025]"
|
|
style={{
|
|
backgroundImage:
|
|
"url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\")",
|
|
}}
|
|
/>
|
|
|
|
{/* ─── Content ─────────────────────────────────────────── */}
|
|
<div className="relative z-10 flex flex-1 flex-col items-center justify-center overflow-auto px-6 py-10">
|
|
<div className="mb-10 flex flex-col items-center text-center">
|
|
{/* App icon — the H glass. Drop-shadow tinted with violet so
|
|
the icon reads as continuous with the gradient backdrop. */}
|
|
<img
|
|
src="/app-icon.png"
|
|
alt=""
|
|
aria-hidden
|
|
className="h-[88px] w-[88px] select-none"
|
|
style={{
|
|
filter:
|
|
'drop-shadow(0 8px 24px rgba(123,107,227,0.30)) drop-shadow(0 2px 4px rgba(0,0,0,0.24))',
|
|
}}
|
|
draggable={false}
|
|
/>
|
|
|
|
<h1
|
|
className="mt-6 bg-clip-text text-4xl font-bold uppercase tracking-[0.16em] text-transparent"
|
|
style={{
|
|
backgroundImage: 'var(--gradient-brand-wordmark)',
|
|
fontFamily: 'var(--font-headline)',
|
|
}}
|
|
>
|
|
Heicode
|
|
</h1>
|
|
|
|
<p
|
|
className="mt-4 max-w-sm text-[13px] leading-relaxed text-[var(--color-text-secondary)]"
|
|
style={{ fontFamily: 'var(--font-headline)' }}
|
|
>
|
|
{t('login.tagline')}
|
|
</p>
|
|
</div>
|
|
|
|
{!hasFetched && isLoading ? (
|
|
<div className="text-sm text-[var(--color-text-tertiary)]">
|
|
{t('common.loading')}
|
|
</div>
|
|
) : null}
|
|
|
|
{/* docs/product-package/08 §"登录" + §"客户端不应该出现的内容":
|
|
Login screen only shows the Heicode provider. The legacy
|
|
`official` (Claude Official) preset was removed from the
|
|
provider type entirely; no filter needed any more. */}
|
|
{hasFetched && providers.length === 0 ? (
|
|
<div className="max-w-sm rounded-[var(--radius-md)] border border-[var(--color-error)]/30 bg-[var(--color-error-container)] p-4 text-sm text-[var(--color-error)]">
|
|
{t('login.errors.noProviders')}
|
|
</div>
|
|
) : null}
|
|
|
|
{hasFetched && providers.length > 0 ? (
|
|
<div className="grid w-full max-w-sm grid-cols-1 gap-4">
|
|
{providers.map((provider) => (
|
|
<ProviderLoginCard key={provider.id} provider={provider} />
|
|
))}
|
|
</div>
|
|
) : null}
|
|
|
|
{error ? (
|
|
<div className="mt-6 max-w-sm rounded-[var(--radius-md)] border border-[var(--color-error)]/30 bg-[var(--color-error-container)] px-4 py-3 text-xs text-[var(--color-error)]">
|
|
{error}
|
|
</div>
|
|
) : null}
|
|
|
|
<p className="mt-12 max-w-sm text-center text-[11px] leading-relaxed text-[var(--color-text-tertiary)]">
|
|
{t('login.footer.heicodeProvidesModels')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|