mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-07 02:58:11 +00:00
Align desktop login with web split layout
This commit is contained in:
parent
d5fddf5611
commit
41c0a9333b
1 changed files with 286 additions and 141 deletions
|
|
@ -1,6 +1,12 @@
|
|||
"use client"
|
||||
|
||||
import { Logo, LogoFull } from "@ui/assets/Logo"
|
||||
import { Logo } from "@ui/assets/Logo"
|
||||
import {
|
||||
ClaudeDesktopIcon,
|
||||
GoogleDrive,
|
||||
MCPIcon,
|
||||
Notion,
|
||||
} from "@ui/assets/icons"
|
||||
import { ExternalAuthButton } from "@ui/button/external-auth"
|
||||
import { Badge } from "@ui/components/badge"
|
||||
import { Button } from "@ui/components/button"
|
||||
|
|
@ -100,153 +106,250 @@ export default function LoginPage() {
|
|||
const isAuthBusy = isBrowserAuthPending || isSubmitting
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col overflow-hidden bg-[#030912] text-foreground">
|
||||
<header
|
||||
data-tauri-drag-region
|
||||
className="flex h-24 shrink-0 items-center justify-between px-8"
|
||||
>
|
||||
<LogoFull className="h-8 w-[270px] max-w-[46vw] text-white" />
|
||||
<Button
|
||||
asChild
|
||||
variant="newDefault"
|
||||
className="h-14 rounded-[24px] px-7 text-xl text-white"
|
||||
<main className="grid h-screen min-h-screen overflow-hidden bg-[#030912] text-foreground lg:grid-cols-[65%_35%]">
|
||||
<DesktopLoginToolsPanel />
|
||||
|
||||
<section className="relative z-10 flex min-h-0 min-w-0 flex-col">
|
||||
<header
|
||||
data-tauri-drag-region
|
||||
className="flex h-[86px] shrink-0 items-center justify-between px-6"
|
||||
>
|
||||
<a
|
||||
href="https://supermemory.ai/docs"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
<div className="flex min-w-0 items-center">
|
||||
<Logo className="h-7 shrink-0" />
|
||||
<p className="ml-2 truncate font-semibold text-white/90 text-xl leading-none">
|
||||
supermemory
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
variant="newDefault"
|
||||
className="h-11 rounded-2xl px-4 text-base text-white"
|
||||
>
|
||||
Memory API ↗
|
||||
</a>
|
||||
</Button>
|
||||
</header>
|
||||
<a
|
||||
href="https://console.supermemory.ai"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Memory API <span className="text-xs">↗</span>
|
||||
</a>
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
<main className="flex flex-1 flex-col items-center justify-center gap-8 px-8 pb-10">
|
||||
<section className="max-w-[720px] text-center">
|
||||
<h1 className="text-balance font-medium text-[40px] leading-tight text-[#f7f9fc]">
|
||||
Never forget anything, anywhere
|
||||
<span className="block text-[#4BA0FA]">with supermemory</span>
|
||||
</h1>
|
||||
<p className="mt-4 text-muted-foreground/50 text-xl">
|
||||
Save from Chrome, Notion, X - search it all in one place.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="flex w-full max-w-[700px] flex-col items-center rounded-[32px] bg-linear-to-b from-[#06101F] to-[#030912] px-16 py-12 shadow-[1.5px_1.5px_20px_0_rgba(0,0,0,0.65),1px_1.5px_2px_0_rgba(128,189,255,0.07)_inset,-0.5px_-1.5px_4px_0_rgba(0,35,73,0.40)_inset]">
|
||||
<div className="relative w-full max-w-[560px]">
|
||||
<div className={isBrowserAuthPending ? "invisible" : undefined}>
|
||||
<div className="mb-2 flex justify-end">
|
||||
<Badge className="h-8 rounded-[10px] px-4 text-base">
|
||||
Last used
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-5">
|
||||
<ExternalAuthButton
|
||||
authIcon={<GoogleIcon />}
|
||||
authProvider="Google"
|
||||
className="w-full"
|
||||
disabled={isAuthBusy}
|
||||
onClick={startBrowserAuth}
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<ExternalAuthButton
|
||||
authIcon={<GithubIcon />}
|
||||
authProvider="Github"
|
||||
className="w-full"
|
||||
disabled={isAuthBusy}
|
||||
onClick={startBrowserAuth}
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<TextSeparator text="OR" />
|
||||
|
||||
<form
|
||||
className="flex flex-col gap-6"
|
||||
onSubmit={onBrowserAuthSubmit}
|
||||
>
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(event) => setEmail(event.target.value)}
|
||||
placeholder="your@email.com"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
className="h-[68px] rounded-[18px] border-[#17202e] bg-[#040a14]/70 px-8 text-[22px] text-foreground placeholder:text-muted-foreground/45"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="h-[70px] rounded-[18px] bg-linear-to-r from-[#2935ff] to-[#2f78ff] text-[22px] text-white hover:from-[#3440ff] hover:to-[#3b83ff]"
|
||||
disabled={isAuthBusy}
|
||||
>
|
||||
<Logo className="size-7" />
|
||||
Log in with Supermemory
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-muted-foreground/50 text-sm">
|
||||
By continuing, you agree to our{" "}
|
||||
<a
|
||||
className="underline underline-offset-4 hover:text-muted-foreground"
|
||||
href="https://supermemory.ai/terms"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Terms
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
className="underline underline-offset-4 hover:text-muted-foreground"
|
||||
href="https://supermemory.ai/privacy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
{error ? (
|
||||
<p className="rounded-xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-center text-destructive text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{desktopDevAuthEnabled ? (
|
||||
<form className="space-y-3 text-left" onSubmit={onSubmit}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="api-key">Development API key</Label>
|
||||
<Input
|
||||
id="api-key"
|
||||
value={token}
|
||||
onChange={(event) => setToken(event.target.value)}
|
||||
placeholder="sm_..."
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={!token.trim() || isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Checking..." : "Continue with key"}
|
||||
</Button>
|
||||
</form>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center justify-center px-6 pb-10">
|
||||
<div className="w-full max-w-[390px]">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-balance font-medium text-[28px] leading-tight text-[#f7f9fc]">
|
||||
Never forget anything, anywhere
|
||||
<span className="block text-[#4BA0FA]">with supermemory</span>
|
||||
</h1>
|
||||
<p className="mt-4 text-muted-foreground/50 text-sm">
|
||||
Save from Chrome, Notion, X - search it all in one place.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isBrowserAuthPending ? (
|
||||
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-[14px] bg-[#030912]/90 backdrop-blur-sm">
|
||||
<Loader2 className="size-6 animate-spin text-muted-foreground" />
|
||||
<p className="text-muted-foreground text-sm">Redirecting...</p>
|
||||
<div className="relative rounded-[22px] bg-linear-to-b from-[#06101F] to-[#030912] px-8 py-8 shadow-[1.5px_1.5px_20px_0_rgba(0,0,0,0.65),1px_1.5px_2px_0_rgba(128,189,255,0.07)_inset,-0.5px_-1.5px_4px_0_rgba(0,35,73,0.40)_inset]">
|
||||
<div className={isBrowserAuthPending ? "invisible" : undefined}>
|
||||
<div className="mb-2 flex justify-end">
|
||||
<Badge className="h-5 rounded-md px-2 text-[10px]">
|
||||
Last used
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<ExternalAuthButton
|
||||
authIcon={<GoogleIcon />}
|
||||
authProvider="Google"
|
||||
className="w-full"
|
||||
disabled={isAuthBusy}
|
||||
onClick={startBrowserAuth}
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<ExternalAuthButton
|
||||
authIcon={<GithubIcon />}
|
||||
authProvider="Github"
|
||||
className="w-full"
|
||||
disabled={isAuthBusy}
|
||||
onClick={startBrowserAuth}
|
||||
type="button"
|
||||
/>
|
||||
|
||||
<TextSeparator text="OR" />
|
||||
|
||||
<form
|
||||
className="flex flex-col gap-6"
|
||||
onSubmit={onBrowserAuthSubmit}
|
||||
>
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(event) => setEmail(event.target.value)}
|
||||
placeholder="your@email.com"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
className="h-14 rounded-xl border-[#17202e] bg-[#040a14]/70 px-6 text-base text-foreground placeholder:text-muted-foreground/45"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="h-14 rounded-xl bg-linear-to-r from-[#2935ff] to-[#2f78ff] text-lg text-white hover:from-[#3440ff] hover:to-[#3b83ff]"
|
||||
disabled={isAuthBusy}
|
||||
>
|
||||
<Logo className="size-5" />
|
||||
Log in with Supermemory
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<p className="text-center text-muted-foreground/50 text-sm">
|
||||
By continuing, you agree to our{" "}
|
||||
<a
|
||||
className="underline underline-offset-4 hover:text-muted-foreground"
|
||||
href="https://supermemory.ai/terms"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Terms
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a
|
||||
className="underline underline-offset-4 hover:text-muted-foreground"
|
||||
href="https://supermemory.ai/privacy"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Privacy Policy
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
{error ? (
|
||||
<p className="rounded-xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-center text-destructive text-sm">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{desktopDevAuthEnabled ? (
|
||||
<details className="group rounded-xl border border-white/5 bg-white/[0.02] px-4 py-3">
|
||||
<summary className="cursor-pointer text-muted-foreground text-xs transition-colors hover:text-foreground">
|
||||
Development API key
|
||||
</summary>
|
||||
<form
|
||||
className="mt-4 space-y-3 text-left"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="api-key">API key</Label>
|
||||
<Input
|
||||
id="api-key"
|
||||
value={token}
|
||||
onChange={(event) => setToken(event.target.value)}
|
||||
placeholder="sm_..."
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={!token.trim() || isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Checking..." : "Continue with key"}
|
||||
</Button>
|
||||
</form>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isBrowserAuthPending ? (
|
||||
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-[14px] bg-[#030912]/90 backdrop-blur-sm">
|
||||
<Loader2 className="size-6 animate-spin text-muted-foreground" />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Redirecting...
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function DesktopLoginToolsPanel() {
|
||||
return (
|
||||
<aside className="relative hidden min-h-0 overflow-hidden border-white/[0.06] border-r lg:block">
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(ellipse 130% 100% at 50% 45%, rgba(70,155,255,0.26) 0%, rgba(35,100,210,0.15) 38%, rgba(8,22,45,0.58) 68%, #030912 100%), radial-gradient(ellipse 90% 70% at 50% 100%, rgba(50,130,240,0.18) 0%, transparent 65%)",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_52%,rgba(85,150,255,0.22),transparent_24%),radial-gradient(circle_at_48%_52%,rgba(255,255,255,0.18),transparent_8%)] opacity-80" />
|
||||
<div className="absolute inset-0 bg-linear-to-b from-[#071024]/20 via-transparent to-[#030912]/35" />
|
||||
|
||||
<div className="relative z-10 h-full">
|
||||
<FloatingToolNode x={14} y={22} label="Chrome">
|
||||
<ChromeLogo className="size-5" />
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={84} y={18} label="Notion">
|
||||
<Notion className="size-5" />
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={11} y={53} label="Google Drive">
|
||||
<GoogleDrive className="size-5" />
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={88} y={45} label="Claude">
|
||||
<ClaudeDesktopIcon className="size-5" />
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={22} y={84} label="MCP">
|
||||
<MCPIcon className="size-5" />
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={68} y={70} label="Raycast">
|
||||
<RaycastMark className="size-5" />
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={20} y={38} label="Code">
|
||||
<span className="font-semibold text-[#ff9a63] text-lg">▦</span>
|
||||
</FloatingToolNode>
|
||||
<FloatingToolNode x={58} y={14} label="OpenCode">
|
||||
<span className="font-semibold text-[#b8c4d8] text-sm">□</span>
|
||||
</FloatingToolNode>
|
||||
|
||||
<div className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2">
|
||||
<div className="relative flex size-36 items-center justify-center rounded-full bg-[radial-gradient(circle_at_68%_34%,rgba(145,230,255,0.9),rgba(48,81,255,0.92)_44%,rgba(14,29,90,0.84)_72%,transparent_73%)] shadow-[0_0_54px_rgba(65,135,255,0.6),inset_12px_16px_24px_rgba(255,255,255,0.13)] blur-[1px]">
|
||||
<div className="absolute inset-4 rounded-full bg-[#244cff]/50 blur-xl" />
|
||||
<Logo className="relative z-10 size-8 opacity-85" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="-translate-x-1/2 absolute bottom-8 left-1/2 whitespace-nowrap text-center text-sm text-white/45">
|
||||
One memory layer - context from any tool, everywhere you need it.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
function FloatingToolNode({
|
||||
x,
|
||||
y,
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
x: number
|
||||
y: number
|
||||
label: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className="-translate-x-1/2 -translate-y-1/2 absolute z-10"
|
||||
style={{ left: `${x}%`, top: `${y}%` }}
|
||||
title={label}
|
||||
>
|
||||
<div className="flex size-12 items-center justify-center rounded-xl border border-white/12 bg-[#0c1628]/85 shadow-[inset_0_1px_0_rgba(255,255,255,0.1),0_6px_20px_rgba(0,0,0,0.22)] backdrop-blur-md">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -299,6 +402,48 @@ function GithubIcon() {
|
|||
)
|
||||
}
|
||||
|
||||
function ChromeLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 190.5 190.5">
|
||||
<title>Chrome</title>
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M95.25 142.87c26.3 0 47.63-21.32 47.63-47.63s-21.32-47.63-47.63-47.63-47.63 21.32-47.63 47.63 21.32 47.63 47.63 47.63z"
|
||||
/>
|
||||
<path
|
||||
fill="#229342"
|
||||
d="m54.01 119.07-41.24-71.43a95.23 95.23 0 0 0-.003 95.25 95.23 95.23 0 0 0 82.5 47.61l41.24-71.43a47.62 47.62 0 0 1-82.5 0z"
|
||||
/>
|
||||
<path
|
||||
fill="#fbc116"
|
||||
d="m136.5 119.07-41.24 71.43a95.23 95.23 0 0 0 82.49-47.62A95.24 95.24 0 0 0 190.5 95.25a95.24 95.24 0 0 0-12.77-47.62H95.25a47.62 47.62 0 0 1 41.25 71.44z"
|
||||
/>
|
||||
<path
|
||||
fill="#1a73e8"
|
||||
d="M95.25 132.96c20.82 0 37.7-16.88 37.7-37.71S116.08 57.55 95.25 57.55 57.55 74.43 57.55 95.25s16.88 37.71 37.7 37.71z"
|
||||
/>
|
||||
<path
|
||||
fill="#e33b2e"
|
||||
d="M95.25 47.63h82.48A95.24 95.24 0 0 0 142.87 12.76 95.23 95.23 0 0 0 95.25 0a95.22 95.22 0 0 0-82.48 47.64l41.24 71.43a47.62 47.62 0 0 1 41.24-71.44z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function RaycastMark({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 28 28" fill="none">
|
||||
<title>Raycast</title>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7 18.08V21L0 14L1.46 12.54L7 18.08ZM9.92 21H7L14 28L15.46 26.54L9.92 21ZM26.54 15.46L28 14L14 0L12.54 1.47L18.08 7H14.73L10.86 3.15L9.4 4.61L11.81 7.01H10.13V17.88H20.99V16.2L23.4 18.6L24.86 17.14L20.99 13.27V9.93L26.54 15.46ZM7.73 6.28L6.26 7.74L7.83 9.3L9.29 7.84L7.73 6.28ZM20.16 18.71L18.7 20.17L20.27 21.74L21.73 20.28L20.16 18.71ZM4.6 9.41L3.13 10.87L7 14.74V11.81L4.6 9.41ZM16.19 21.01H13.27L17.13 24.87L18.6 23.41L16.19 21.01Z"
|
||||
fill="#FF6363"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function formatError(error: unknown, fallback: string) {
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue