-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
557 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
"use client"; | ||
import { Badge } from "@/components/ui/badge"; | ||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; | ||
import { useHeadlessAIS } from "@/hooks/contexts/useHeadlessAIS"; | ||
import { | ||
getDoorAccessQR, | ||
getOSAAccessToken, | ||
getOSACode, | ||
getParcelInformation, | ||
} from "@/lib/headless_ais/inthu"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { useState } from "react"; | ||
import { Skeleton } from "@/components/ui/skeleton"; | ||
import { QRCodeSVG } from "qrcode.react"; | ||
import Barcode from "react-barcode"; | ||
|
||
const StudentIDPage = () => { | ||
const [tab, setTab] = useState("door"); | ||
const { ais, getACIXSTORE, user } = useHeadlessAIS(); | ||
|
||
const { | ||
data: refresh, | ||
error: refreshError, | ||
isLoading: refreshLoading, | ||
} = useQuery({ | ||
queryKey: ["osa_refreshtoken", user?.studentid], | ||
queryFn: async () => { | ||
const ACIXSTORE = await getACIXSTORE(); | ||
if (!ACIXSTORE) { | ||
throw new Error("Failed to get token"); | ||
} | ||
const res = await getOSACode(ACIXSTORE); | ||
if (!res) { | ||
throw new Error("Failed to get token"); | ||
} | ||
return res; | ||
}, | ||
// expire after 1 day | ||
staleTime: 86400000, | ||
// don't retry on error | ||
retry: false, | ||
}); | ||
const { | ||
data: token, | ||
error: tokenError, | ||
isLoading: tokenLoading, | ||
} = useQuery({ | ||
queryKey: ["osa_accesstoken", refresh?.user_id], | ||
queryFn: async () => { | ||
if (!refresh) { | ||
throw new Error("Refresh token not found"); | ||
} | ||
const access = await getOSAAccessToken( | ||
refresh.user_id, | ||
refresh.refreshToken, | ||
); | ||
if (!access) { | ||
throw new Error("Failed to get token"); | ||
} | ||
return { ...access, user_id: refresh.user_id }; | ||
}, | ||
// refresh every 1 hour | ||
refetchInterval: 3600000, | ||
enabled: !!refresh, | ||
// don't retry on error | ||
retry: false, | ||
}); | ||
|
||
const { | ||
data: qrString, | ||
error, | ||
isLoading, | ||
} = useQuery({ | ||
queryKey: ["parcels", token?.user_id], | ||
queryFn: async () => { | ||
if (!token) { | ||
throw new Error("Token not found"); | ||
} | ||
const data = await getDoorAccessQR( | ||
token.authToken, | ||
token.deviceId, | ||
token.session_id, | ||
); | ||
return data; | ||
}, | ||
enabled: !!token && tab === "door", | ||
// refresh only every 10 sec | ||
refetchInterval: 10000, | ||
// don't retry on error | ||
retry: false, | ||
}); | ||
|
||
const isLoadingStuff = refreshLoading || tokenLoading || isLoading; | ||
|
||
return ( | ||
<div className="flex flex-col px-4"> | ||
<div className="flex flex-col gap-2"> | ||
<div className=""> | ||
<h1 className="text-xl font-bold">國立清華大學</h1> | ||
</div> | ||
<Tabs defaultValue="door" value={tab} onValueChange={setTab}> | ||
<div className="flex flex-col gap-2"> | ||
<TabsContent value="door"> | ||
<div className="flex flex-col items-center"> | ||
{isLoadingStuff || !qrString ? ( | ||
<Skeleton className="h-60 w-60"></Skeleton> | ||
) : ( | ||
<img | ||
src={qrString} | ||
alt="door access qr" | ||
className="w-60 h-60" | ||
/> | ||
)} | ||
</div> | ||
</TabsContent> | ||
<TabsContent value="studentid"> | ||
{user?.studentid ? ( | ||
<div className="flex flex-col items-center gap-4"> | ||
<Barcode | ||
width={2} | ||
height={44} | ||
value={user.studentid} | ||
displayValue={false} | ||
/> | ||
<QRCodeSVG className="h-40 w-40" value={user.studentid} /> | ||
</div> | ||
) : ( | ||
<div className="flex flex-col items-center gap-4"> | ||
<Skeleton className="h-16 w-60"></Skeleton> | ||
<Skeleton className="h-40 w-40"></Skeleton> | ||
</div> | ||
)} | ||
</TabsContent> | ||
<TabsList className="w-full justify-evenly mb-4 fixed md:relative bottom-16 md:bottom-auto left-0 md:left-auto"> | ||
<TabsTrigger value="door" className="flex-1"> | ||
門禁 | ||
</TabsTrigger> | ||
<TabsTrigger value="studentid" className="flex-1"> | ||
學號 | ||
</TabsTrigger> | ||
</TabsList> | ||
</div> | ||
</Tabs> | ||
<div className="flex flex-col gap-2"> | ||
<div> | ||
<h1 className="text-lg font-bold">{user?.name_zh}</h1> | ||
<h1>{user?.name_en}</h1> | ||
</div> | ||
<div className="flex gap-2"> | ||
<div className="text-sm font-thin">科系</div> | ||
<h2 className="text-sm">{user?.department}</h2> | ||
</div> | ||
<div className="flex gap-2"> | ||
<div className="text-sm font-thin">學號</div> | ||
<h2 className="text-sm">{user?.studentid}</h2> | ||
</div> | ||
<div className="flex gap-2"> | ||
<div className="text-sm font-thin">年級</div> | ||
<h2 className="text-sm">{user?.grade}</h2> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default StudentIDPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
"use client"; | ||
import { Badge } from "@/components/ui/badge"; | ||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; | ||
import { useHeadlessAIS } from "@/hooks/contexts/useHeadlessAIS"; | ||
import { | ||
getOSAAccessToken, | ||
getOSACode, | ||
getParcelInformation, | ||
} from "@/lib/headless_ais/inthu"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { useState } from "react"; | ||
|
||
const ParcelPage = () => { | ||
const [tab, setTab] = useState("pending"); | ||
const { ais, getACIXSTORE, user } = useHeadlessAIS(); | ||
|
||
const { | ||
data: refresh, | ||
error: refreshError, | ||
isLoading: refreshLoading, | ||
} = useQuery({ | ||
queryKey: ["osa_refreshtoken", user?.studentid], | ||
queryFn: async () => { | ||
const ACIXSTORE = await getACIXSTORE(); | ||
if (!ACIXSTORE) { | ||
throw new Error("Failed to get token"); | ||
} | ||
const res = await getOSACode(ACIXSTORE); | ||
if (!res) { | ||
throw new Error("Failed to get token"); | ||
} | ||
return res; | ||
}, | ||
// expire after 1 day | ||
staleTime: 86400000, | ||
// don't retry on error | ||
retry: false, | ||
}); | ||
const { | ||
data: token, | ||
error: tokenError, | ||
isLoading: tokenLoading, | ||
} = useQuery({ | ||
queryKey: ["osa_accesstoken", refresh?.user_id], | ||
queryFn: async () => { | ||
if (!refresh) { | ||
throw new Error("Refresh token not found"); | ||
} | ||
const access = await getOSAAccessToken( | ||
refresh.user_id, | ||
refresh.refreshToken, | ||
); | ||
if (!access) { | ||
throw new Error("Failed to get token"); | ||
} | ||
return { ...access, user_id: refresh.user_id }; | ||
}, | ||
// refresh every 1 hour | ||
refetchInterval: 3600000, | ||
enabled: !!refresh, | ||
// don't retry on error | ||
retry: false, | ||
}); | ||
|
||
const { | ||
data: parcels = [], | ||
error, | ||
isLoading, | ||
} = useQuery({ | ||
queryKey: ["parcels", token?.user_id], | ||
queryFn: async () => { | ||
if (!token) { | ||
throw new Error("Token not found"); | ||
} | ||
const data = await getParcelInformation( | ||
token.authToken, | ||
token.deviceId, | ||
token.session_id, | ||
); | ||
return data; | ||
}, | ||
enabled: !!token, | ||
// refresh only every 5 minutes, | ||
refetchInterval: 300000, | ||
// don't retry on error | ||
retry: false, | ||
}); | ||
|
||
const pendingParcels = parcels.filter( | ||
(parcel) => parcel.statusText === "pending", | ||
); | ||
const archiveParcels = parcels.filter( | ||
(parcel) => parcel.statusText === "已取件", | ||
); | ||
|
||
const handleTabChange = (value: string) => { | ||
setTab(value); | ||
}; | ||
return ( | ||
<div className="flex flex-col px-4"> | ||
<Tabs defaultValue="pending" value={tab} onValueChange={handleTabChange}> | ||
<TabsList className="w-full justify-evenly mb-4"> | ||
<TabsTrigger className="flex-1" value="pending"> | ||
Pending | ||
</TabsTrigger> | ||
<TabsTrigger className="flex-1" value="archive"> | ||
Archivee | ||
</TabsTrigger> | ||
</TabsList> | ||
<TabsContent value="pending"></TabsContent> | ||
<TabsContent value="archive"> | ||
{archiveParcels.map((parcel) => ( | ||
<div | ||
className="flex flex-col bg-white rounded-lg shadow-md p-4 mb-4" | ||
key={parcel.barcode} | ||
> | ||
<div className="flex flex-row gap-1"> | ||
<Badge className="rounded-md" variant="secondary"> | ||
{parcel.statusText} | ||
</Badge> | ||
<span className="text-sm text-gray-500">{parcel.takeTime}</span> | ||
</div> | ||
<span className="text-lg font-semibold">{parcel.name}</span> | ||
<span className="text-sm text-gray-500"> | ||
{parcel.studentNumber} | ||
</span> | ||
<span className="text-sm text-gray-500"> | ||
{parcel.logistic} {parcel.barcode} | ||
</span> | ||
</div> | ||
))} | ||
</TabsContent> | ||
</Tabs> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ParcelPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.