From f0477bcd110cfc73d27227bc785c0ef0c1f97378 Mon Sep 17 00:00:00 2001 From: ImJustChew Date: Fri, 23 Aug 2024 02:22:58 +0800 Subject: [PATCH 1/2] feat: redesigned apps page with categories, with new apps --- src/app/[lang]/(mods-pages)/apps/page.tsx | 259 ++++++++++++++++------ src/const/apps.ts | 170 ++++++++++---- src/dictionaries/en.json | 10 +- src/dictionaries/zh.json | 10 +- 4 files changed, 337 insertions(+), 112 deletions(-) diff --git a/src/app/[lang]/(mods-pages)/apps/page.tsx b/src/app/[lang]/(mods-pages)/apps/page.tsx index 9d936670..f086d893 100644 --- a/src/app/[lang]/(mods-pages)/apps/page.tsx +++ b/src/app/[lang]/(mods-pages)/apps/page.tsx @@ -1,85 +1,204 @@ "use client"; -import { apps } from "@/const/apps"; -import { getDictionary } from "@/dictionaries/dictionaries"; -import { LangProps } from "@/types/pages"; -import Link from "next/link"; -import { Info, ArrowRight } from "lucide-react"; -import FavouriteApp from "./Favorite"; -import React from "react"; +import { apps, categories } from "@/const/apps"; +import { ExternalLink, Settings, Star } from "lucide-react"; +import React, { useCallback } from "react"; import useDictionary from "@/dictionaries/useDictionary"; import { useHeadlessAIS } from "@/hooks/contexts/useHeadlessAIS"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { useSettings } from "@/hooks/contexts/settings"; +import { useRouter } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; +import { toast } from "@/components/ui/use-toast"; +import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; -const AppList = ({ params: { lang } }: LangProps) => { +const AppItem = ({ app }: { app: (typeof apps)[number] }) => { + const { language } = useSettings(); + const { ais, getACIXSTORE } = useHeadlessAIS(); + const router = useRouter(); const dict = useDictionary(); - const { ais } = useHeadlessAIS(); + const [aisLoading, setAisLoading] = React.useState(false); + + const onItemClicked = useCallback(async () => { + if (app.ais) { + if (!ais.enabled) { + toast({ title: dict.ccxp.not_logged_in_error }); + return; + } + + setAisLoading(true); + const token = await getACIXSTORE(); + if (!token) { + setAisLoading(false); + return; + } + + // if starts with http, open in new tab + if (app.href.startsWith("https://www.ccxp.nthu.edu.tw")) { + // Redirect user + const redirect_url = app.href + `?ACIXSTORE=${token}`; + console.log(redirect_url); + const link = document.createElement("a"); + link.href = redirect_url; + link.target = "_blank"; + link.click(); + } else { + router.push(app.href); + } + setAisLoading(false); + } else { + router.push(app.href); + } + }, [router, app, ais, getACIXSTORE, dict]); return ( -
-
-

{dict.applist.title}

- {apps - .filter((m) => (m.ais ? !!ais.enabled : true)) - .map((app) => ( -
- +
+ +
+
+

+ {language == "zh" ? app.title_zh : app.title_en} +

+ {app.href.startsWith("https://www.ccxp.nthu.edu.tw") && ( +

+ {dict.applist.to_ccxp} +

+ )} +
+ + +
+
+ {/*
*/} + -
- -
-
-

- {lang == "zh" ? app.title_zh : app.title_en} -

-
- -
- -
+ + + +

+ {dict.ccxp.logging_in_please_wait} +

- ))} - {/* */} -
- {!ais.enabled && ( - - -
-

還有更多功能!

-

- 到設定同步校務資訊系統后,可以直接在這裏使用校務資訊系統的功能! -

-
- - - - - -
+
+ +
+
+ ); +}; + +const AppList = () => { + const dict = useDictionary(); + const { language, pinnedApps, toggleApp } = useSettings(); + const { ais } = useHeadlessAIS(); + + return ( +
+
+
+
+

+ {dict.applist.pinned_apps_title} +

+ + + + + +
+

+ {dict.applist.edit_pinned_apps_title} +

+ +
+ {apps.map((app) => ( +
+
+ +
+
+

+ {language == "zh" ? app.title_zh : app.title_en} +

+
+
+ +
+
+ ))} +
+
+
+
+
+
+
+ {apps + .filter((app) => pinnedApps.includes(app.id)) + .map((app) => ( + + ))} +
+ {pinnedApps.length == 0 && ( +

+ {dict.applist.empty_pinned_apps_reminder} +

)} - - 沒有你要的功能? - - 快到 - - Github - - 提出你的想法吧 - -
+ {Object.keys(categories).map((category) => ( +
+

+ {categories[category][`title_${language}`]} +

+
+ {apps + .filter((m) => m.category === category) + .map((app) => ( + + ))} +
+
+ ))}
); diff --git a/src/const/apps.ts b/src/const/apps.ts index 070c7cd8..7aa16b2a 100644 --- a/src/const/apps.ts +++ b/src/const/apps.ts @@ -14,18 +14,69 @@ import { TicketCheck, CalendarCheck2, CalendarSearch, + Book, + BookOpen, + ClipboardCheck, + Download, + Globe, + HeartHandshakeIcon, + HandCoins, } from "lucide-react"; -export const apps = [ +export const categories: { + [id: string]: { title_zh: string; title_en: string }; +} = { + campuslife: { + title_zh: "校園生活", + title_en: "Campus Life", + }, + course: { + title_zh: "課程資訊", + title_en: "Courses", + }, + apply: { + title_zh: "申請系統", + title_en: "School Systems", + }, + club: { + title_zh: "社團", + title_en: "Clubs", + }, + other: { + title_zh: "其他", + title_en: "Others", + }, +}; + +export const apps: { + id: string; + category: keyof typeof categories; + title_zh: string; + title_en: string; + href: string; + Icon: React.FC; + ais?: boolean; + target?: string; +}[] = [ + { + id: "courses", + category: "course", + title_zh: "課程查詢", + title_en: "Course Search", + href: "/courses", + Icon: BookOpen, + }, { id: "venues", - title_zh: "地點", + category: "course", + title_zh: "地點相關課程", title_en: "Venues", href: "/venues", Icon: MapPin, }, { id: "bus", + category: "campuslife", title_zh: "公車", title_en: "Bus", href: "/bus", @@ -33,104 +84,147 @@ export const apps = [ }, { id: "shops", + category: "campuslife", title_zh: "餐廳及服務", title_en: "Shops", href: "/shops", Icon: Store, }, + { + id: "grades", + category: "course", + title_zh: "你的成績", + title_en: "Your Grades", + href: "/student/grades", + Icon: Clipboard, + ais: true, + }, { id: "clubs_info", + category: "club", title_zh: "社團資訊", title_en: "Clubs Information", href: "https://outrageous-savory-d52.notion.site/d33567eea7814fc6b91744351eb2ba6a", Icon: Gamepad, }, { - id: "grades", - title_zh: "你的成績", - title_en: "Your Grades", - href: "/student/grades", - Icon: Clipboard, + id: "nthuclub", + category: "club", + title_zh: "清大社團管理系統", + title_en: "Clubs Management System", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/nthuclub.php", + Icon: Club, + target: "_blank", + ais: true, + }, + { + id: "leave_application", + category: "campuslife", + title_zh: "請假系統", + title_en: "Leave Application", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/oauth_stuleave.php", + Icon: ClipboardCheck, + target: "_blank", ais: true, }, { id: "coursereg", + category: "course", title_zh: "選課系統", title_en: "Course Selection System", - href: - "/ais-redirect/custom?url=" + - encodeURIComponent( - "https://www.ccxp.nthu.edu.tw/ccxp/COURSE/JH/7/7.1/7.1.3/JH713001.php", - ), + href: "https://www.ccxp.nthu.edu.tw/ccxp/COURSE/JH/7/7.1/7.1.3/JH713001.php", Icon: CalendarCheck2, target: "_blank", ais: true, }, { id: "coursereg", - title_zh: "選課選上/剩餘名額/待亂數人數統計", + category: "course", + title_zh: "選課待亂數名額查詢", title_en: "Realtime Course Enrollment Status", - href: - "/ais-redirect/custom?url=" + - encodeURIComponent( - "https://www.ccxp.nthu.edu.tw/ccxp/COURSE/JH/7/7.2/7.2.7/JH727001.php", - ), + href: "https://www.ccxp.nthu.edu.tw/ccxp/COURSE/JH/7/7.2/7.2.7/JH727001.php", Icon: CalendarSearch, - target: "_blank", ais: true, }, { id: "eform", + category: "apply", title_zh: "電子表單系統", title_en: "E-Form System", - href: "/ais-redirect/SSO_LINK/oauth_eform.php", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/oauth_eform.php", Icon: Paperclip, - target: "_blank", ais: true, }, { id: "reach", + category: "campuslife", title_zh: "校園通報網", title_en: "Campus Defect Report", - href: "/ais-redirect/SSO_LINK/campusnotice.php", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/campusnotice.php", Icon: AlertOctagon, - target: "_blank", ais: true, }, { id: "park", + category: "apply", title_zh: "駐警隊車輛辦證系統", title_en: "Vehicle Registration System", - href: "/ais-redirect/SSO_LINK/park.php", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/park.php", Icon: Car, - target: "_blank", ais: true, }, { id: "card", - title_zh: "校園電子票證掛失/補發/密碼變更系統", + category: "apply", + title_zh: "校園學生證掛失/管理系統", title_en: "Student ID Management", - href: "/ais-redirect/SSO_LINK/card.php", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/card.php", Icon: CreditCard, - target: "_blank", ais: true, }, { - id: "nthuclub", - title_zh: "清大社團管理系統", - title_en: "NTHU Clubs", - href: "/ais-redirect/SSO_LINK/nthuclub.php", - Icon: Club, - target: "_blank", + id: "netsys", + category: "apply", + title_zh: "網路系統組線上服務", + title_en: "Network System Services", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/netsys.php", + Icon: Network, ais: true, }, { id: "netsys", - title_zh: "網路系統組線上服務", - title_en: "Network System Services", - href: "/ais-redirect/netsys.php", + category: "apply", + title_zh: " Office365帳號申請", + title_en: "Office 365 Account Application", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/oauth_office365.php", Icon: Network, - target: "_blank", + ais: true, + }, + { + id: "netsys", + category: "apply", + title_zh: " 教室借用申請", + title_en: "Classroom Renting", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/spacebooking.php", + Icon: HandCoins, + ais: true, + }, + { + id: "counselling", + category: "other", + title_zh: " 諮商輔導預約申請", + title_en: "Counselling Appointment", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/counsel.php", + Icon: HeartHandshakeIcon, + ais: true, + }, + { + id: "mentorship", + category: "other", + title_zh: "明燈平台", + title_en: "NTHU Mentorship Platform", + href: "https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/SSO_LINK/strategy_goal_2.php", + Icon: Globe, ais: true, }, ]; diff --git a/src/dictionaries/en.json b/src/dictionaries/en.json index e8b80cff..24bc7403 100644 --- a/src/dictionaries/en.json +++ b/src/dictionaries/en.json @@ -188,7 +188,11 @@ } }, "applist": { - "title": "Apps" + "title": "Apps", + "to_ccxp": "NTHU AIS", + "empty_pinned_apps_reminder": "Pin your most used apps here!", + "pinned_apps_title": "Pinned Apps", + "edit_pinned_apps_title": "Edit Pinned Apps" }, "dialogs": { "DownloadTimetableDialog": { @@ -270,7 +274,9 @@ "IncorrectCredentials": "Incorrect Login Credentials", "CaptchaError": "CAPTCHA Decoding went wrong, Please retry in 15 minutes.", "UnknownError": "Something went wrong, please try checking on the NTHU AIS website." - } + }, + "logging_in_please_wait": "Logging into your Academic Information System Account...", + "not_logged_in_error": "You are not logged in" }, "cancel": "Cancel", "privacy_policy": "Privacy Policy" diff --git a/src/dictionaries/zh.json b/src/dictionaries/zh.json index cf67b46d..f899187b 100644 --- a/src/dictionaries/zh.json +++ b/src/dictionaries/zh.json @@ -188,7 +188,11 @@ } }, "applist": { - "title": "功能列表" + "title": "功能列表", + "to_ccxp": "校務系統鏈接", + "empty_pinned_apps_reminder": "可以到右上角加快捷功能哦~", + "pinned_apps_title": "快捷功能", + "edit_pinned_apps_title": "修改快捷功能" }, "dialogs": { "DownloadTimetableDialog": { @@ -270,7 +274,9 @@ "IncorrectCredentials": "學號或密碼錯誤", "CaptchaError": "驗證碼解讀失敗, 請在15分鐘后重試。", "UnknownError": "發生了問題,但不知道爲什麽 :( 請到校務系統登入試試看。" - } + }, + "logging_in_please_wait": "正在幫你登入校務資訊系統,請稍等...", + "not_logged_in_error": "還未連接校務資訊系統" }, "cancel": "取消", "privacy_policy": "隱私權政策" From 1a8f6206709920cb83ed168a039a2a3045bffc0d Mon Sep 17 00:00:00 2001 From: ImJustChew Date: Fri, 23 Aug 2024 02:32:33 +0800 Subject: [PATCH 2/2] feat: updated app design for today page too --- src/app/[lang]/(mods-pages)/apps/AppItem.tsx | 119 +++++++++++++++++++ src/app/[lang]/(mods-pages)/apps/page.tsx | 104 +--------------- src/components/Today/TodaySchedule.tsx | 19 +-- 3 files changed, 124 insertions(+), 118 deletions(-) create mode 100644 src/app/[lang]/(mods-pages)/apps/AppItem.tsx diff --git a/src/app/[lang]/(mods-pages)/apps/AppItem.tsx b/src/app/[lang]/(mods-pages)/apps/AppItem.tsx new file mode 100644 index 00000000..d4a9683b --- /dev/null +++ b/src/app/[lang]/(mods-pages)/apps/AppItem.tsx @@ -0,0 +1,119 @@ +import { apps } from "@/const/apps"; +import { ExternalLink } from "lucide-react"; +import React from "react"; +import { useCallback } from "react"; +import useDictionary from "@/dictionaries/useDictionary"; +import { useHeadlessAIS } from "@/hooks/contexts/useHeadlessAIS"; +import { useSettings } from "@/hooks/contexts/settings"; +import { useRouter } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { toast } from "@/components/ui/use-toast"; + +const AppItem = ({ + app, + mini = false, +}: { + app: (typeof apps)[number]; + mini?: boolean; +}) => { + const { language } = useSettings(); + const { ais, getACIXSTORE } = useHeadlessAIS(); + const router = useRouter(); + const dict = useDictionary(); + const [aisLoading, setAisLoading] = React.useState(false); + + const onItemClicked = useCallback(async () => { + if (app.ais) { + if (!ais.enabled) { + toast({ title: dict.ccxp.not_logged_in_error }); + return; + } + + setAisLoading(true); + const token = await getACIXSTORE(); + if (!token) { + setAisLoading(false); + return; + } + + // if starts with http, open in new tab + if (app.href.startsWith("https://www.ccxp.nthu.edu.tw")) { + // Redirect user + const redirect_url = app.href + `?ACIXSTORE=${token}`; + console.log(redirect_url); + const link = document.createElement("a"); + link.href = redirect_url; + link.target = "_blank"; + link.click(); + } else { + router.push(app.href); + } + setAisLoading(false); + } else { + router.push(app.href); + } + }, [router, app, ais, getACIXSTORE, dict]); + + return ( +
+
+ +
+
+

+ {language == "zh" ? app.title_zh : app.title_en} +

+ {!mini && app.href.startsWith("https://www.ccxp.nthu.edu.tw") && ( +

+ {dict.applist.to_ccxp} +

+ )} +
+ + +
+
+ {/*
*/} + + + + +

+ {dict.ccxp.logging_in_please_wait} +

+
+
+
+
+
+ ); +}; + +export default AppItem; diff --git a/src/app/[lang]/(mods-pages)/apps/page.tsx b/src/app/[lang]/(mods-pages)/apps/page.tsx index f086d893..28f6ffbb 100644 --- a/src/app/[lang]/(mods-pages)/apps/page.tsx +++ b/src/app/[lang]/(mods-pages)/apps/page.tsx @@ -1,117 +1,17 @@ "use client"; import { apps, categories } from "@/const/apps"; -import { ExternalLink, Settings, Star } from "lucide-react"; -import React, { useCallback } from "react"; +import { Settings, Star } from "lucide-react"; import useDictionary from "@/dictionaries/useDictionary"; -import { useHeadlessAIS } from "@/hooks/contexts/useHeadlessAIS"; import { useSettings } from "@/hooks/contexts/settings"; -import { useRouter } from "next/navigation"; import { cn } from "@/lib/utils"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; -import { toast } from "@/components/ui/use-toast"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; - -const AppItem = ({ app }: { app: (typeof apps)[number] }) => { - const { language } = useSettings(); - const { ais, getACIXSTORE } = useHeadlessAIS(); - const router = useRouter(); - const dict = useDictionary(); - const [aisLoading, setAisLoading] = React.useState(false); - - const onItemClicked = useCallback(async () => { - if (app.ais) { - if (!ais.enabled) { - toast({ title: dict.ccxp.not_logged_in_error }); - return; - } - - setAisLoading(true); - const token = await getACIXSTORE(); - if (!token) { - setAisLoading(false); - return; - } - - // if starts with http, open in new tab - if (app.href.startsWith("https://www.ccxp.nthu.edu.tw")) { - // Redirect user - const redirect_url = app.href + `?ACIXSTORE=${token}`; - console.log(redirect_url); - const link = document.createElement("a"); - link.href = redirect_url; - link.target = "_blank"; - link.click(); - } else { - router.push(app.href); - } - setAisLoading(false); - } else { - router.push(app.href); - } - }, [router, app, ais, getACIXSTORE, dict]); - - return ( -
-
- -
-
-

- {language == "zh" ? app.title_zh : app.title_en} -

- {app.href.startsWith("https://www.ccxp.nthu.edu.tw") && ( -

- {dict.applist.to_ccxp} -

- )} -
- - -
-
- {/*
*/} - - - - -

- {dict.ccxp.logging_in_please_wait} -

-
-
-
-
-
- ); -}; +import AppItem from "./AppItem"; const AppList = () => { const dict = useDictionary(); const { language, pinnedApps, toggleApp } = useSettings(); - const { ais } = useHeadlessAIS(); return (
diff --git a/src/components/Today/TodaySchedule.tsx b/src/components/Today/TodaySchedule.tsx index 2d192d14..151169a5 100644 --- a/src/components/Today/TodaySchedule.tsx +++ b/src/components/Today/TodaySchedule.tsx @@ -21,6 +21,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import useTime from "@/hooks/useTime"; import { NoClassPickedReminder } from "./NoClassPickedReminder"; import { TimetableItemDrawer } from "@/components/Timetable/TimetableItemDrawer"; +import AppItem from "@/app/[lang]/(mods-pages)/apps/AppItem"; const getRangeOfDays = (start: Date, end: Date) => { const days = []; @@ -132,23 +133,9 @@ const TodaySchedule: FC<{ if (applist.length == 0) return <>; return (
-

- {dict.applist.title} -

-
+
{applist.map((app, index) => ( - -
-
- -
-
-

- {language == "zh" ? app.title_zh : app.title_en} -

-
-
- + ))}