mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: Add Telegram binding storage and auth
This commit is contained in:
@@ -6,7 +6,9 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
|
||||
import type { ServerUrlResult } from '@/hooks/useServerUrl'
|
||||
|
||||
type LoginPromptProps = {
|
||||
onLogin: (token: string) => void
|
||||
mode?: 'login' | 'bind'
|
||||
onLogin?: (token: string) => void
|
||||
onBind?: (token: string) => Promise<void>
|
||||
baseUrl: string
|
||||
serverUrl: string | null
|
||||
setServerUrl: (input: string) => ServerUrlResult
|
||||
@@ -15,6 +17,7 @@ type LoginPromptProps = {
|
||||
}
|
||||
|
||||
export function LoginPrompt(props: LoginPromptProps) {
|
||||
const isBindMode = props.mode === 'bind'
|
||||
const [accessToken, setAccessToken] = useState('')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -35,13 +38,26 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
// Validate the token by attempting to authenticate
|
||||
const client = new ApiClient('', { baseUrl: props.baseUrl })
|
||||
await client.authenticate({ accessToken: trimmedToken })
|
||||
// If successful, pass the token to parent
|
||||
props.onLogin(trimmedToken)
|
||||
if (isBindMode) {
|
||||
if (!props.onBind) {
|
||||
setError('Binding is unavailable.')
|
||||
return
|
||||
}
|
||||
await props.onBind(trimmedToken)
|
||||
} else {
|
||||
// Validate the token by attempting to authenticate
|
||||
const client = new ApiClient('', { baseUrl: props.baseUrl })
|
||||
await client.authenticate({ accessToken: trimmedToken })
|
||||
// If successful, pass the token to parent
|
||||
if (!props.onLogin) {
|
||||
setError('Login is unavailable.')
|
||||
return
|
||||
}
|
||||
props.onLogin(trimmedToken)
|
||||
}
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Authentication failed')
|
||||
const fallbackMessage = isBindMode ? 'Binding failed' : 'Authentication failed'
|
||||
setError(e instanceof Error ? e.message : fallbackMessage)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -76,73 +92,81 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
|
||||
const displayError = error || props.error
|
||||
const serverSummary = props.serverUrl ?? `${props.baseUrl} (same origin)`
|
||||
const title = isBindMode ? 'Bind Telegram' : 'HAPI'
|
||||
const subtitle = isBindMode
|
||||
? 'Enter your access token to bind this Telegram account'
|
||||
: 'Enter your access token to continue'
|
||||
const submitLabel = isBindMode ? 'Bind' : 'Sign In'
|
||||
const helpText = 'Use the CLI_API_TOKEN from your server configuration'
|
||||
|
||||
return (
|
||||
<div className="relative h-full flex items-center justify-center p-4">
|
||||
<div className="absolute right-4 top-4 z-10">
|
||||
<Dialog open={isServerDialogOpen} onOpenChange={setIsServerDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
Server
|
||||
<span className="text-[10px] uppercase tracking-wide text-[var(--app-hint)]">
|
||||
{props.serverUrl ? 'Custom' : 'Default'}
|
||||
</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Server URL</DialogTitle>
|
||||
<DialogDescription>
|
||||
Set the hapi server origin for API and live updates.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSaveServer} className="space-y-4">
|
||||
<div className="text-xs text-[var(--app-hint)]">
|
||||
Current: {serverSummary}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium">Server origin</label>
|
||||
<input
|
||||
type="url"
|
||||
value={serverInput}
|
||||
onChange={(e) => {
|
||||
setServerInput(e.target.value)
|
||||
setServerError(null)
|
||||
}}
|
||||
placeholder="https://hapi.example.com"
|
||||
className="w-full px-3 py-2.5 rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] text-[var(--app-fg)] placeholder:text-[var(--app-hint)] focus:outline-none focus:ring-2 focus:ring-[var(--app-button)] focus:border-transparent"
|
||||
/>
|
||||
<div className="text-[11px] text-[var(--app-hint)]">
|
||||
Use http(s) only. Any path is ignored.
|
||||
{!isBindMode && (
|
||||
<div className="absolute right-4 top-4 z-10">
|
||||
<Dialog open={isServerDialogOpen} onOpenChange={setIsServerDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
Server
|
||||
<span className="text-[10px] uppercase tracking-wide text-[var(--app-hint)]">
|
||||
{props.serverUrl ? 'Custom' : 'Default'}
|
||||
</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Server URL</DialogTitle>
|
||||
<DialogDescription>
|
||||
Set the hapi server origin for API and live updates.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSaveServer} className="space-y-4">
|
||||
<div className="text-xs text-[var(--app-hint)]">
|
||||
Current: {serverSummary}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{serverError && (
|
||||
<div className="text-sm text-red-500">
|
||||
{serverError}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium">Server origin</label>
|
||||
<input
|
||||
type="url"
|
||||
value={serverInput}
|
||||
onChange={(e) => {
|
||||
setServerInput(e.target.value)
|
||||
setServerError(null)
|
||||
}}
|
||||
placeholder="https://hapi.example.com"
|
||||
className="w-full px-3 py-2.5 rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] text-[var(--app-fg)] placeholder:text-[var(--app-hint)] focus:outline-none focus:ring-2 focus:ring-[var(--app-button)] focus:border-transparent"
|
||||
/>
|
||||
<div className="text-[11px] text-[var(--app-hint)]">
|
||||
Use http(s) only. Any path is ignored.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{props.serverUrl && (
|
||||
<Button type="button" variant="outline" onClick={handleClearServer}>
|
||||
Use same origin
|
||||
</Button>
|
||||
{serverError && (
|
||||
<div className="text-sm text-red-500">
|
||||
{serverError}
|
||||
</div>
|
||||
)}
|
||||
<Button type="submit">
|
||||
Save server
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{props.serverUrl && (
|
||||
<Button type="button" variant="outline" onClick={handleClearServer}>
|
||||
Use same origin
|
||||
</Button>
|
||||
)}
|
||||
<Button type="submit">
|
||||
Save server
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full max-w-sm space-y-6">
|
||||
{/* Header */}
|
||||
<div className="text-center space-y-2">
|
||||
<div className="text-2xl font-semibold">HAPI</div>
|
||||
<div className="text-2xl font-semibold">{title}</div>
|
||||
<div className="text-sm text-[var(--app-hint)]">
|
||||
Enter your access token to continue
|
||||
{subtitle}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -153,7 +177,7 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
type="password"
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
placeholder="Access Token"
|
||||
placeholder={isBindMode ? 'CLI_API_TOKEN' : 'Access Token'}
|
||||
autoComplete="current-password"
|
||||
disabled={isLoading}
|
||||
className="w-full px-3 py-2.5 rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] text-[var(--app-fg)] placeholder:text-[var(--app-hint)] focus:outline-none focus:ring-2 focus:ring-[var(--app-button)] focus:border-transparent disabled:opacity-50"
|
||||
@@ -175,17 +199,17 @@ export function LoginPrompt(props: LoginPromptProps) {
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Spinner size="sm" label={null} className="text-[var(--app-button-text)]" />
|
||||
Signing in…
|
||||
{isBindMode ? 'Binding...' : 'Signing in...'}
|
||||
</>
|
||||
) : (
|
||||
'Sign In'
|
||||
submitLabel
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Help text */}
|
||||
<div className="text-xs text-[var(--app-hint)] text-center">
|
||||
Use the CLI_API_TOKEN from your server configuration
|
||||
{helpText}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user