Files
heicode-win/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx
T
chenchenandClaude Opus 4.7 50830c2393 feat(desktop): 0.1.1 — UX fixes + multiSelect + clean About + version bump
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>
2026-05-13 14:34:26 +08:00

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>
)
}