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,