feat: add feishu multi-user auth
This commit is contained in:
@@ -4,6 +4,7 @@ import type { FormEvent } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
ArrowRight,
|
||||
Building2,
|
||||
CheckCircle2,
|
||||
Eye,
|
||||
EyeOff,
|
||||
@@ -14,8 +15,14 @@ import { AnimatedLoginCharacters, type LoginCharacterMood } from "@/components/l
|
||||
import { OasisCanvas } from "@/components/login/oasis-canvas"
|
||||
|
||||
type LoginStatus = "idle" | "loading" | "success"
|
||||
type AuthConfig = {
|
||||
auth_configured?: boolean
|
||||
password_enabled?: boolean
|
||||
feishu_enabled?: boolean
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
const [authConfig, setAuthConfig] = useState<AuthConfig | null>(null)
|
||||
const [username, setUsername] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [remember, setRemember] = useState(true)
|
||||
@@ -25,6 +32,21 @@ export default function LoginPage() {
|
||||
const [status, setStatus] = useState<LoginStatus>("idle")
|
||||
const [eyeOffset, setEyeOffset] = useState({ x: 0, y: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
fetch("/api/auth/config", { cache: "no-store", credentials: "include" })
|
||||
.then((res) => res.ok ? res.json() : null)
|
||||
.then((data) => {
|
||||
if (!cancelled && data) setAuthConfig(data)
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setAuthConfig(null)
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
const centerX = window.innerWidth / 2
|
||||
@@ -38,6 +60,8 @@ export default function LoginPage() {
|
||||
}, [])
|
||||
|
||||
const disabled = status === "loading" || status === "success"
|
||||
const feishuEnabled = Boolean(authConfig?.feishu_enabled)
|
||||
const passwordEnabled = authConfig?.password_enabled ?? true
|
||||
|
||||
const mood: LoginCharacterMood = useMemo(() => {
|
||||
if (status === "success") return "success"
|
||||
@@ -50,6 +74,7 @@ export default function LoginPage() {
|
||||
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
setHasError(false)
|
||||
if (!passwordEnabled) return
|
||||
if (!username.trim() || !password) {
|
||||
setHasError(true)
|
||||
return
|
||||
@@ -75,6 +100,11 @@ export default function LoginPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function onFeishuLogin() {
|
||||
setStatus("loading")
|
||||
window.location.href = "/api/auth/feishu/start?next=/"
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="login-page login-page--oasis login-page--source relative min-h-screen overflow-hidden text-white">
|
||||
<OasisCanvas />
|
||||
@@ -89,7 +119,29 @@ export default function LoginPage() {
|
||||
<AnimatedLoginCharacters mood={mood} eyeOffset={eyeOffset} />
|
||||
</div>
|
||||
<form className="login-source-form-pane w-full" onSubmit={onSubmit}>
|
||||
<div className="space-y-3">
|
||||
{feishuEnabled ? (
|
||||
<button
|
||||
className="mb-3 flex h-11 w-full items-center justify-center gap-2 rounded-[8px] bg-white px-4 text-base font-semibold text-black shadow-xl shadow-black/25 transition hover:bg-[#f5efe3] focus:outline-none focus:ring-2 focus:ring-[#d6b36a]/60 disabled:cursor-wait disabled:opacity-70"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={onFeishuLogin}
|
||||
>
|
||||
<Building2 className="h-4 w-4" />
|
||||
飞书免登录
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
{feishuEnabled && passwordEnabled ? (
|
||||
<div className="mb-3 flex items-center gap-3 text-xs text-white/35">
|
||||
<span className="h-px flex-1 bg-white/10" />
|
||||
<span>备用账号</span>
|
||||
<span className="h-px flex-1 bg-white/10" />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{passwordEnabled ? (
|
||||
<div className="space-y-3">
|
||||
<label className="block">
|
||||
<span className="flex h-11 items-center gap-3 rounded-[8px] border border-white/10 bg-black/25 px-3 text-white transition focus-within:border-[#d6b36a] focus-within:bg-black/35 focus-within:ring-2 focus-within:ring-[#d6b36a]/25">
|
||||
<UserRound className="h-4 w-4 text-white/45" />
|
||||
@@ -135,9 +187,11 @@ export default function LoginPage() {
|
||||
</button>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<label className="mt-3 flex cursor-pointer items-center gap-2 text-sm text-white/60">
|
||||
{passwordEnabled ? (
|
||||
<label className="mt-3 flex cursor-pointer items-center gap-2 text-sm text-white/60">
|
||||
<input
|
||||
className="h-4 w-4 rounded border-white/20 bg-black/30 accent-[#c89b3c]"
|
||||
type="checkbox"
|
||||
@@ -146,7 +200,8 @@ export default function LoginPage() {
|
||||
onChange={(event) => setRemember(event.target.checked)}
|
||||
/>
|
||||
<span>保持登录</span>
|
||||
</label>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
{status === "success" ? (
|
||||
<div className="mt-3">
|
||||
@@ -156,13 +211,15 @@ export default function LoginPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
{passwordEnabled ? (
|
||||
<button
|
||||
className="mt-4 flex h-11 w-full items-center justify-center gap-2 rounded-[8px] bg-white px-4 text-base font-semibold text-black shadow-xl shadow-black/25 transition hover:bg-[#f5efe3] focus:outline-none focus:ring-2 focus:ring-[#d6b36a]/60 disabled:cursor-wait disabled:opacity-70"
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</button>
|
||||
</button>
|
||||
) : null}
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user