From 781dc171b015d257700b91165de4476a6884422b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 02:50:37 +0000 Subject: [PATCH 1/3] Initial plan From 542ddb630c98b94a1cda109ea5829fd05a70287c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 03:00:58 +0000 Subject: [PATCH 2/3] Refactor duplicated code - Create shared utilities for auth and date formatting --- src/pages/repair/EventDetail.tsx | 4 +- src/pages/repair/RepairAdmin.tsx | 27 ++++-------- src/pages/repair/RepairHistoryCard.tsx | 8 ++-- src/pages/repair/RepairHistoryPage.tsx | 17 ++------ src/pages/repair/TicketDetail.tsx | 20 ++------- src/pages/repair/TicketForm.tsx | 16 ++----- src/utils/date.ts | 34 +++++++++++++++ src/utils/repair.ts | 60 ++++++++++++++++++++++++++ 8 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 src/utils/date.ts create mode 100644 src/utils/repair.ts diff --git a/src/pages/repair/EventDetail.tsx b/src/pages/repair/EventDetail.tsx index 22ad863..ad9751e 100644 --- a/src/pages/repair/EventDetail.tsx +++ b/src/pages/repair/EventDetail.tsx @@ -3,8 +3,8 @@ import type { components } from "../../types/saturday" import { saturdayClient } from "../../utils/client" import { Textarea, Input, Chip, Skeleton } from "@heroui/react" import type { PublicMember } from "../../store/member" -import dayjs from "dayjs" import { EventStatus, UserEventAction } from "../../types/event" +import { formatDateTime } from "../../utils/date" type PublicEvent = components["schemas"]["PublicEvent"] type EventLog = components["schemas"]["EventLog"] @@ -36,7 +36,7 @@ function EventLogItem(props: {
- {dayjs(props.eventLog.gmtCreate).format("YYYY-MM-DD HH:mm")} + {formatDateTime(props.eventLog.gmtCreate)}
diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx index 4374ebe..bb82852 100644 --- a/src/pages/repair/RepairAdmin.tsx +++ b/src/pages/repair/RepairAdmin.tsx @@ -27,13 +27,14 @@ import { useAsyncList } from "@react-stately/data" import type { components } from "../../types/saturday" import { saturdayClient } from "../../utils/client" import EventDetail, { EventStatusChip, type EventDetailRef } from "./EventDetail" -import dayjs from "dayjs" import { EventStatus, UserEventStatus, type RepairEvent } from "../../types/event" import { makeLogtoClient } from "../../utils/auth" import type { PublicMember } from "../../store/member" import type { UserInfoResponse } from "@logto/browser" import { getAvailableEventActions, type EventAction, type IdentityContext } from "./EventAction" import { ExportExcelModal } from "./ExportEventDialog" +import { requireRepairRole } from "../../utils/repair" +import { formatDateTime } from "../../utils/date" type PublicEvent = components["schemas"]["PublicEvent"] @@ -181,11 +182,6 @@ function TicketDetailDrawer(props: { ) } -export const validateRepairRole = (roles: string[]) => { - const acceptableRoles = ["repair admin", "repair member"] - return roles.some(role => acceptableRoles.includes(role.toLowerCase())) -} - export default function App() { const [isLoading, setIsLoading] = useState(true) const [page, setPage] = useState(1) @@ -201,20 +197,15 @@ export default function App() { useEffect(() => { const check = async () => { const adminPath = "/repair/admin" - const authenticated = await makeLogtoClient().isAuthenticated() - if (!authenticated) { - window.location.href = `/repair/login-hint?redirectUrl=${adminPath}` + const userInfo = await requireRepairRole(adminPath) + + if (!userInfo) { return } - const res = await makeLogtoClient().getIdTokenClaims() + + setUserInfo(userInfo) const token = await makeLogtoClient().getAccessToken() setToken(token) - const hasRole = validateRepairRole(res.roles) - if (!hasRole) { - window.location.href = `/repair/login-hint?redirectUrl=${adminPath}` - return - } - setUserInfo(res) const { data } = await saturdayClient.GET("/member", { params: { @@ -353,7 +344,7 @@ export default function App() {
- { dayjs(event.gmtCreate).format("YYYY-MM-DD HH:mm") } + { formatDateTime(event.gmtCreate) }
{event.model && ( @@ -414,7 +405,7 @@ export default function App() { case "gmtCreate": return ( - {dayjs(cellValue).format("YYYY-MM-DD HH:mm")} + {formatDateTime(cellValue)} ) case "status": diff --git a/src/pages/repair/RepairHistoryCard.tsx b/src/pages/repair/RepairHistoryCard.tsx index c6b3dbb..9b10d6c 100644 --- a/src/pages/repair/RepairHistoryCard.tsx +++ b/src/pages/repair/RepairHistoryCard.tsx @@ -1,7 +1,7 @@ import { Card, CardBody, Chip } from "@heroui/react" import { EventStatusChip } from "./EventDetail" -import dayjs from "dayjs" import type { components } from "../../types/saturday" +import { formatDate, formatDateTime, formatShortDateTime } from "../../utils/date" type PublicEvent = components["schemas"]["PublicEvent"] @@ -29,7 +29,7 @@ export default function RepairHistoryCard({ event, onViewDetail }: RepairHistory {event.size && {event.size}}
- {dayjs(event.gmtCreate).format("YYYY-MM-DD")} + {formatDate(event.gmtCreate)}
@@ -41,10 +41,10 @@ export default function RepairHistoryCard({ event, onViewDetail }: RepairHistory
- 创建于 {dayjs(event.gmtCreate).format("YYYY-MM-DD HH:mm")} + 创建于 {formatDateTime(event.gmtCreate)} {event.gmtModified !== event.gmtCreate && ( - 更新于 {dayjs(event.gmtModified).format("MM-DD HH:mm")} + 更新于 {formatShortDateTime(event.gmtModified)} )}
diff --git a/src/pages/repair/RepairHistoryPage.tsx b/src/pages/repair/RepairHistoryPage.tsx index 1ccd066..4fac986 100644 --- a/src/pages/repair/RepairHistoryPage.tsx +++ b/src/pages/repair/RepairHistoryPage.tsx @@ -3,6 +3,7 @@ import { Button, Spinner } from "@heroui/react" import { makeLogtoClient } from "../../utils/auth" import UserRepairHistory from "./UserRepairHistory" import type { UserInfoResponse } from "@logto/browser" +import { checkAuthAndRedirect } from "../../utils/repair" export default function RepairHistoryPage() { const [userInfo, setUserInfo] = useState(null) @@ -14,22 +15,10 @@ export default function RepairHistoryPage() { const checkAuthStatus = async () => { try { - const logtoClient = makeLogtoClient() - const authenticated = await logtoClient.isAuthenticated() - - if (authenticated) { - const claims = await logtoClient.getIdTokenClaims() + const claims = await checkAuthAndRedirect("/repair/history") + if (claims) { setUserInfo(claims) } - else { - // Redirect to login if not authenticated - window.location.href = "/repair/login-hint?redirectUrl=/repair/history" - } - } - catch (error) { - console.error("Error checking auth status:", error) - // Redirect to login on error - window.location.href = "/repair/login-hint?redirectUrl=/repair/history" } finally { setLoading(false) diff --git a/src/pages/repair/TicketDetail.tsx b/src/pages/repair/TicketDetail.tsx index 2d38a71..870f332 100644 --- a/src/pages/repair/TicketDetail.tsx +++ b/src/pages/repair/TicketDetail.tsx @@ -6,6 +6,7 @@ import EventDetail from "./EventDetail" import EditRepairModal from "./EditRepairModal" import type { UserInfoResponse } from "@logto/browser" import type { components } from "../../types/saturday" +import { checkAuthAndRedirect } from "../../utils/repair" type PublicEvent = components["schemas"]["PublicEvent"] @@ -32,24 +33,11 @@ export default function TicketDetail() { const checkAuthStatus = async () => { try { - const logtoClient = makeLogtoClient() - const authenticated = await logtoClient.isAuthenticated() - - if (authenticated) { - const claims = await logtoClient.getIdTokenClaims() + const redirectUrl = `/repair/ticket-detail${window.location.search}` + const claims = await checkAuthAndRedirect(redirectUrl) + if (claims) { setUserInfo(claims) } - else { - // Redirect to login if not authenticated - const redirectUrl = `/repair/ticket-detail${window.location.search}` - window.location.href = `/repair/login-hint?redirectUrl=${encodeURIComponent(redirectUrl)}` - } - } - catch (error) { - console.error("Error checking auth status:", error) - // Redirect to login on error - const redirectUrl = `/repair/ticket-detail${window.location.search}` - window.location.href = `/repair/login-hint?redirectUrl=${encodeURIComponent(redirectUrl)}` } finally { setLoading(false) diff --git a/src/pages/repair/TicketForm.tsx b/src/pages/repair/TicketForm.tsx index 5b03089..5dad13a 100644 --- a/src/pages/repair/TicketForm.tsx +++ b/src/pages/repair/TicketForm.tsx @@ -3,7 +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" +import { checkAuthAndRedirect } from "../../utils/repair" type TicketFormData = { model?: string @@ -275,17 +275,9 @@ export default function App() { useEffect(() => { const check = async () => { const createRepairPath = "/repair/create-ticket" - try { - const [res, err] = await safe(makeLogtoClient().getIdTokenClaims()) - if (err) { - window.location.href = `/repair/login-hint?redirectUrl=${createRepairPath}` - return - } - setUserInfo(res) - } - catch (error) { - console.error("Error checking authentication:", error) - window.location.href = `/repair/login-hint?redirectUrl=${createRepairPath}` + const claims = await checkAuthAndRedirect(createRepairPath) + if (claims) { + setUserInfo(claims) } } check() diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..2a9a9bc --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,34 @@ +import dayjs from "dayjs" + +/** + * Common date format patterns used across the application + */ +export const DateFormats = { + /** Format: YYYY-MM-DD HH:mm (e.g., 2024-01-15 13:45) */ + DATETIME: "YYYY-MM-DD HH:mm", + /** Format: YYYY-MM-DD (e.g., 2024-01-15) */ + DATE: "YYYY-MM-DD", + /** Format: MM-DD HH:mm (e.g., 01-15 13:45) */ + SHORT_DATETIME: "MM-DD HH:mm", +} as const + +/** + * Formats a date string or Date object to datetime format (YYYY-MM-DD HH:mm) + */ +export const formatDateTime = (date: string | Date): string => { + return dayjs(date).format(DateFormats.DATETIME) +} + +/** + * Formats a date string or Date object to date format (YYYY-MM-DD) + */ +export const formatDate = (date: string | Date): string => { + return dayjs(date).format(DateFormats.DATE) +} + +/** + * Formats a date string or Date object to short datetime format (MM-DD HH:mm) + */ +export const formatShortDateTime = (date: string | Date): string => { + return dayjs(date).format(DateFormats.SHORT_DATETIME) +} diff --git a/src/utils/repair.ts b/src/utils/repair.ts new file mode 100644 index 0000000..b493e9f --- /dev/null +++ b/src/utils/repair.ts @@ -0,0 +1,60 @@ +import { makeLogtoClient } from "./auth" +import type { UserInfoResponse } from "@logto/browser" + +/** + * Validates if the user has repair admin or member role + */ +export const validateRepairRole = (roles: string[]): boolean => { + const acceptableRoles = ["repair admin", "repair member"] + return roles.some(role => acceptableRoles.includes(role.toLowerCase())) +} + +/** + * Checks if user is authenticated and redirects to login-hint if not + * @param redirectUrl - The URL to redirect back to after login + * @returns UserInfoResponse if authenticated, undefined if redirected + */ +export const checkAuthAndRedirect = async ( + redirectUrl: string, +): Promise => { + try { + const logtoClient = makeLogtoClient() + const authenticated = await logtoClient.isAuthenticated() + + if (!authenticated) { + window.location.href = `/repair/login-hint?redirectUrl=${encodeURIComponent(redirectUrl)}` + return undefined + } + + const claims = await logtoClient.getIdTokenClaims() + return claims + } + catch (error) { + console.error("Error checking auth status:", error) + window.location.href = `/repair/login-hint?redirectUrl=${encodeURIComponent(redirectUrl)}` + return undefined + } +} + +/** + * Checks if user is authenticated and has required repair role, redirects if not + * @param redirectUrl - The URL to redirect back to after login + * @returns UserInfoResponse if authenticated and has role, undefined if redirected + */ +export const requireRepairRole = async ( + redirectUrl: string, +): Promise => { + const userInfo = await checkAuthAndRedirect(redirectUrl) + + if (!userInfo) { + return undefined + } + + const hasRole = validateRepairRole(userInfo.roles) + if (!hasRole) { + window.location.href = `/repair/login-hint?redirectUrl=${encodeURIComponent(redirectUrl)}` + return undefined + } + + return userInfo +} From f1b082b45197bc7c25dc4f5a4cdfa0aaafdfddf8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Oct 2025 03:04:22 +0000 Subject: [PATCH 3/3] Add documentation to utility functions clarifying error handling Co-authored-by: wen-templari <52404670+wen-templari@users.noreply.github.com> --- src/utils/repair.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/repair.ts b/src/utils/repair.ts index b493e9f..c1da3a2 100644 --- a/src/utils/repair.ts +++ b/src/utils/repair.ts @@ -13,6 +13,7 @@ export const validateRepairRole = (roles: string[]): boolean => { * Checks if user is authenticated and redirects to login-hint if not * @param redirectUrl - The URL to redirect back to after login * @returns UserInfoResponse if authenticated, undefined if redirected + * @throws Never throws - all errors are handled internally with redirect */ export const checkAuthAndRedirect = async ( redirectUrl: string, @@ -40,6 +41,7 @@ export const checkAuthAndRedirect = async ( * Checks if user is authenticated and has required repair role, redirects if not * @param redirectUrl - The URL to redirect back to after login * @returns UserInfoResponse if authenticated and has role, undefined if redirected + * @throws Never throws - all errors are handled internally with redirect */ export const requireRepairRole = async ( redirectUrl: string,