mirror of
https://github.com/m1ngsama/FUJI.git
synced 2025-12-24 10:51:27 +00:00
Improve authentication callback UI and error handling
- Enhanced callback.astro with loading states and user feedback - Fixed React prop naming in HeaderNavigation (stroke-width -> strokeWidth) - Added safe utility for promise error handling - Improved TicketForm authentication check using safe wrapper 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bfa15f2a42
commit
a2eb15c002
4 changed files with 142 additions and 10 deletions
|
|
@ -65,8 +65,8 @@ export default function App() {
|
|||
</span>
|
||||
{
|
||||
item.target == "_blank" && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="size-4 ml-0.5 inline-block">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="size-4 ml-0.5 inline-block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
@ -103,8 +103,8 @@ export default function App() {
|
|||
</span>
|
||||
{
|
||||
item.target == "_blank" && (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="size-5 ml-1 inline-block">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="size-5 ml-1 inline-block">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,134 @@
|
|||
<div></div>
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro"
|
||||
import RepairHeader from "../components/header/RepairHeader.astro"
|
||||
---
|
||||
|
||||
<BaseLayout primaryTitle="Authentication Callback">
|
||||
<RepairHeader></RepairHeader>
|
||||
<section class="min-h-[70vh]">
|
||||
<div class="section-content my-16 flex flex-col gap-8">
|
||||
<div>
|
||||
<div>
|
||||
<div id="status-text" class="text-xl font-bold">处理登录中...</div>
|
||||
<div id="status-subtitle" class="text-sm text-gray-700">
|
||||
正在验证身份
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div id="loading-spinner" class="flex justify-center my-8">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
<div id="status-message" class="text-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="continue-button-wrapper" class="mt-4 hidden">
|
||||
<button
|
||||
id="continue-button"
|
||||
class="px-6 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition-colors font-medium"
|
||||
>
|
||||
继续
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.text-success {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: #ef4444;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { makeLogtoClient } from "../utils/auth"
|
||||
|
||||
const statusText = document.getElementById("status-text")
|
||||
const statusSubtitle = document.getElementById("status-subtitle")
|
||||
const statusMessage = document.getElementById("status-message")
|
||||
const continueButtonWrapper = document.getElementById("continue-button-wrapper")
|
||||
const continueButton = document.getElementById("continue-button")
|
||||
const loadingSpinner = document.getElementById("loading-spinner")
|
||||
|
||||
const updateStatus = (title, subtitle, message, isError = false) => {
|
||||
statusText.textContent = title
|
||||
statusSubtitle.textContent = subtitle
|
||||
statusMessage.textContent = message
|
||||
|
||||
if (isError) {
|
||||
statusText.classList.add("text-error")
|
||||
statusText.classList.remove("text-success")
|
||||
}
|
||||
else {
|
||||
statusText.classList.add("text-success")
|
||||
statusText.classList.remove("text-error")
|
||||
}
|
||||
}
|
||||
|
||||
const showContinueButton = () => {
|
||||
loadingSpinner.classList.add("hidden")
|
||||
continueButtonWrapper.classList.remove("hidden")
|
||||
}
|
||||
|
||||
const redirectToHome = () => {
|
||||
window.location.assign("/")
|
||||
}
|
||||
|
||||
continueButton.addEventListener("click", redirectToHome)
|
||||
|
||||
// Show manual continue button after 5 seconds as fallback
|
||||
const fallbackTimer = setTimeout(() => {
|
||||
updateStatus(
|
||||
"等待时间较长...",
|
||||
"如果未自动跳转,请手动继续",
|
||||
"你可以点击下方按钮手动继续。",
|
||||
false,
|
||||
)
|
||||
showContinueButton()
|
||||
}, 5000)
|
||||
|
||||
const callbackHandler = async (logtoClient) => {
|
||||
console.log("callbackHandler")
|
||||
try {
|
||||
await logtoClient.handleSignInCallback(window.location.href)
|
||||
|
||||
if (!logtoClient.isAuthenticated) {
|
||||
console.log("User not authenticated")
|
||||
window.location.assign("/")
|
||||
clearTimeout(fallbackTimer)
|
||||
updateStatus(
|
||||
"认证失败",
|
||||
"无法验证你的登录",
|
||||
"无法验证你的登录信息。正在跳转到首页...",
|
||||
true,
|
||||
)
|
||||
showContinueButton()
|
||||
setTimeout(redirectToHome, 2000)
|
||||
return
|
||||
}
|
||||
|
||||
// Authentication successful
|
||||
clearTimeout(fallbackTimer)
|
||||
updateStatus(
|
||||
"登录成功!",
|
||||
"验证完成",
|
||||
"正在跳转到首页...",
|
||||
)
|
||||
showContinueButton()
|
||||
setTimeout(redirectToHome, 1500)
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
window.location.assign("/")
|
||||
clearTimeout(fallbackTimer)
|
||||
updateStatus(
|
||||
"认证错误",
|
||||
"登录过程中出现错误",
|
||||
`登录过程中出现错误:${error.message || "未知错误"}。点击下方按钮返回首页。`,
|
||||
true,
|
||||
)
|
||||
showContinueButton()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -26,5 +139,13 @@ try {
|
|||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
clearTimeout(fallbackTimer)
|
||||
updateStatus(
|
||||
"初始化错误",
|
||||
"无法初始化认证客户端",
|
||||
"无法初始化认证客户端。点击下方按钮返回首页。",
|
||||
true,
|
||||
)
|
||||
showContinueButton()
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { makeLogtoClient } from "../../utils/auth"
|
|||
import type { UserInfoResponse } from "@logto/browser"
|
||||
import { Alert, Form, Input, Button, Textarea } from "@heroui/react"
|
||||
import { saturdayClient } from "../../utils/client"
|
||||
import { safe } from "../../utils/safe"
|
||||
|
||||
type TicketFormData = {
|
||||
model?: string
|
||||
|
|
@ -275,12 +276,11 @@ export default function App() {
|
|||
const check = async () => {
|
||||
const createRepairPath = "/repair/create-ticket"
|
||||
try {
|
||||
const authenticated = await makeLogtoClient().isAuthenticated()
|
||||
if (!authenticated) {
|
||||
const [res, err] = await safe(makeLogtoClient().getIdTokenClaims())
|
||||
if (err) {
|
||||
window.location.href = `/repair/login-hint?redirectUrl=${createRepairPath}`
|
||||
return
|
||||
}
|
||||
const res = await makeLogtoClient().getIdTokenClaims()
|
||||
setUserInfo(res)
|
||||
}
|
||||
catch (error) {
|
||||
|
|
|
|||
11
src/utils/safe.ts
Normal file
11
src/utils/safe.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export async function safe<T>(
|
||||
promise: Promise<T> | (() => T),
|
||||
): Promise<[T | null, Error | null]> {
|
||||
try {
|
||||
const result = promise instanceof Promise ? await promise : promise()
|
||||
return [result, null]
|
||||
}
|
||||
catch (err) {
|
||||
return [null, err instanceof Error ? err : new Error(String(err))]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue