-
-
-
-
diff --git a/src/pages/repair/ticket-detail.astro b/src/pages/repair/ticket-detail.astro
index ef10f30..f079b8a 100644
--- a/src/pages/repair/ticket-detail.astro
+++ b/src/pages/repair/ticket-detail.astro
@@ -6,7 +6,7 @@ import EventDetail from "./EventDetail"
{SITE_TITLE}
- Email: {SITE_EMAIL}
+
+
-
+
-
+
+ {SITE_TITLE} By NBTCA
+
+
+ To be at the intersection of technology and liberal arts.
+
+
+
+ 联系我们:
+ {SITE_EMAIL}
+
-
- © {`2018-${year} ${SITE_NAME}`}
- astro-air-blog
-
-
- 浙ICP备2021030831号
+
diff --git a/src/components/header/NavigationUser.vue b/src/components/header/NavigationUser.vue
index e1ad240..0189eb2 100644
--- a/src/components/header/NavigationUser.vue
+++ b/src/components/header/NavigationUser.vue
@@ -13,6 +13,11 @@ const onSignIn = async () => {
postRedirectUri: window.location.pathname,
})
}
+
+const onGoToAccountManage = async () => {
+ window.open("https://myid.app.nbtca.space/account/aboutme", "_blank")
+}
+
const onSignOut = async () => {
logtoClient.value?.signOut(import.meta.env.PUBLIC_LOGTO_REDIRECT_URL)
}
@@ -66,10 +71,47 @@ onMounted(() => {
+
+
+
+ Copyright © {`${year} ${SITE_NAME}`}
+
+
+ 浙ICP备2021030831号
+
+
diff --git a/src/components/header/RepairHeader.astro b/src/components/header/RepairHeader.astro
index f6ae41d..a86df09 100644
--- a/src/components/header/RepairHeader.astro
+++ b/src/components/header/RepairHeader.astro
@@ -3,15 +3,34 @@ import NavigationUser from "./NavigationUser.vue"
---
diff --git a/src/consts.ts b/src/consts.ts
index 04709de..3335637 100644
--- a/src/consts.ts
+++ b/src/consts.ts
@@ -1,5 +1,5 @@
export const SITE_TITLE = `拔电关机`
export const SITE_EMAIL = "contact@nbtca.space"
-export const SITE_NAME = "Computer Association"
+export const SITE_NAME = "NingboTech University, Computer Association"
export const SITE_DESCRIPTION = "Computer Association"
export const SITE_URL = import.meta.env.PUBLIC_SITE_URL
diff --git a/src/pages/posts/PhoNBT2024.md b/src/pages/posts/PhoNBT2024.md
index c110aae..e401ce8 100644
--- a/src/pages/posts/PhoNBT2024.md
+++ b/src/pages/posts/PhoNBT2024.md
@@ -5,9 +5,7 @@ pubDate: 2024-06-18
description: ' “时间之河滚滚而去,青春韶华转瞬即逝”'
author: 'kongbai'
cover:
- url: 'https://oss.nbtca.space/blog/clas/YQL05614-5xNq3t-mid-ulLkGV.jpeg'
- square: 'https://www.apple.com.cn/newsroom/images/values/environment/Apple-Earth-Day-India-mangrove-Alibaug-canoe_Full-Bleed-Image.jpg.large_2x.jpg'
- alt: 'cover'
+ url: 'https://oss.nbtca.space/blog/clas/YQL05614-5xNq3t-mid-ulLkGV.jpeg'
tags: ["活动","志愿者", "新闻稿", "影留宁理","摄影","毕业"]
theme: 'white'
featured: true
diff --git a/src/pages/posts/_assets/workSummary/IMG_0069.jpeg b/src/pages/posts/_assets/workSummary/IMG_0069.jpeg
new file mode 100644
index 0000000..ddce41c
Binary files /dev/null and b/src/pages/posts/_assets/workSummary/IMG_0069.jpeg differ
diff --git a/src/pages/posts/_assets/workSummary/IMG_0069.jpg b/src/pages/posts/_assets/workSummary/IMG_0069.jpg
deleted file mode 100755
index 25c4b72..0000000
Binary files a/src/pages/posts/_assets/workSummary/IMG_0069.jpg and /dev/null differ
diff --git a/src/pages/posts/blogs/More/Blogroll/99.blogroll.md b/src/pages/posts/blogs/More/Blogroll/99.blogroll.md
deleted file mode 100644
index 9b75aa8..0000000
--- a/src/pages/posts/blogs/More/Blogroll/99.blogroll.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-layout: "../../../../../layouts/MarkdownPost.astro"
-title: 友情链接
-pubDate: 2019-12-25 14:27:01
-permalink: /friends
-article: false
-sidebar: false
-author:
- name: nbtca
- link: https://github.com/nbtca
-tags:
- - 其他
----
-
-
-
-::: cardList
-
-```yaml
-# - name: 麋鹿鲁哟
-# desc: 大道至简,知易行难
-# avatar: ./_assets/82707d577b914020b54384a615b0676e/20200122153807.jpg # 可选
-# link: https://www.cnblogs.com/miluluyo/ # 可选
-# bgColor: '#CBEAFA' # 可选,默认var(--bodyBg)。颜色值有#号时请添加单引号
-# textColor: '#6854A1' # 可选,默认var(--textColor)
-- name: 鲁冠泽的博客
- link: https://cimoc.cn/
- avatar: ./_assets/82707d577b914020b54384a615b0676e/cimoccn.png
- descr: Java, Web。
-- name: 江蕾的博客
- link: https://www.cnblogs.com/JLay
- avatar: ./_assets/82707d577b914020b54384a615b0676e/DoEH51Nj97Ah64a.png
- descr: 前端。
-- name: 黄文轩的博客
- link: https://www.cnblogs.com/N3ptune
- avatar: ./_assets/82707d577b914020b54384a615b0676e/4J9NfH1UZD3sz5I.png
- descr: 网安, Linux和C/C++。
-- name: 陈学书的博客
- link: https://www.cnblogs.com/Flat-White
- avatar: ./_assets/82707d577b914020b54384a615b0676e/VjBGkQ6c58vH4l9.png
- descr: Mac, 人工智能, Web和流水账。
-- name: 王纯的博客
- link: https://chundot.org
- avatar: ./_assets/82707d577b914020b54384a615b0676e/avatar.png
- descr: Web。
-- name: 章晟玮的博客
- link: https://bcscb.xyz/
- avatar: https://cdn.bcscb.xyz/img/1.jpg
- descr: 算法记录。
-```
diff --git a/src/pages/posts/coldCarefulFun.md b/src/pages/posts/coldCarefulFun.md
index c6dbe5a..06046fa 100644
--- a/src/pages/posts/coldCarefulFun.md
+++ b/src/pages/posts/coldCarefulFun.md
@@ -5,7 +5,7 @@ pubDate: 2024-10-15
description: "活动的一些片段"
author: "kongbai"
cover:
- url: ./_assets/post20241016/free_huaji.jpg
+ url: https://oss.nbtca.space/blog/free_huaji_0F7NMW.jpg
alt: "cover"
tags: ["活动"]
---
diff --git a/src/pages/posts/getArch_cn.md b/src/pages/posts/getArch_cn.md
new file mode 100644
index 0000000..e215d99
--- /dev/null
+++ b/src/pages/posts/getArch_cn.md
@@ -0,0 +1,396 @@
+---
+layout: "../../layouts/MarkdownPost.astro"
+title: "如何获取属于你的操作系统"
+pubDate: 2025-05-20
+description: "从零构建ArchLinux"
+author: "小明"
+cover:
+ url: "https://www.svgrepo.com/show/349296/arch-linux.svg"
+ alt: "cover"
+tags: ["指南"]
+---
+
+# 前言
+Arch奉行[极简主义](https://wiki.archlinux.org/title/Arch_Linux),用户可以自行构建任何想要的功能,接下来以实际本机部署为例简单介绍如何构建属于自己的archlinux
+
+# 目录
+
+- [准备工作](#准备工作)
+- [安装介质构建](#安装介质构建)
+- [基础安装](#基础安装)
+ - [1. 使用arch引导盘](#1.使用arch引导盘)
+ - [2. UEFI检查](#2.uefi检查)
+ - [3. 联网](#3.-联网)
+ - [4. 测试连通性](#4.-测试连通性)
+ - [5. 同步系统时钟](#5.-同步系统时钟)
+ - [6. 换国内源](#6.-换国内源(在国际互联网内可忽略))
+ - [7. 建立btrfs分区](#7.-建立btrfs分区)
+ - [8. 挂载,请依序从根目录开始挂载](#8.-挂载,请依序从根目录开始挂载)
+ - [9. 安装系统](#9.-安装系统)
+ - [10. 生成fstab文件](#10.-生成fstab文件)
+ - [11. 进入写好的新系统](#11.-进入写好的新系统)
+ - [12. 设置主机名称与时区](#12.-设置主机名称与时区)
+ - [13. 硬件时间设置](#13.-硬件时间设置)
+ - [14. 设置区域](#14.-设置区域)
+ - [15. 设置root密码](#15.-设置root密码)
+ - [16. 安装微码](#16.-安装微码)
+ - [17. 安装Grub引导](#17.-安装grub引导)
+ - [18. 完成安装](#18.-完成安装)
+
+# 准备工作
+
+电脑,U盘(任何移动存储介质),网络,基本检索能力
+
+1. 不论你采取何种镜像方案,即使是离线版镜像构建我也推荐你准备网络条件,这样可以确保内核和工具的更新,当然如果你足够熟练也可自行抉择
+
+2. 如果是无线网络,请确保wifi名称是自己记得住的英文名,因为tty环境下是没有办法显示中文的,会变成一个个没办法识别的方块
+
+3. 如果你希望在同一块硬盘上安装双系统,请为archlinux留下足够大小的硬盘空间,为了给自己留下安装其他软件的余地,请至少[准备100GB(貌似还没有补充相关教学,请自行搜索)](请补充磁盘分区教学);并且确保EFI分区容量不小于256MB,或者[新增一个额外的挂载点](https://wiki.archlinux.org/title/EFI_system_partition)
+
+4. 检查Win10分区是否启用Bitlocker加密,请提前获取恢复密钥,并且关闭电源计划中的快速启动!
+
+> 在操作前请确保仔细阅读并对不太理解的检索学习,谨慎操作,及时备份,数据无价。
+
+# 安装介质的构建
+
+1. 仅推荐从[archlinux官方的镜像源](https://archlinux.org/download/)下载,请注意,arch是滚动发行版
+
+2. 如果你要自行编译,请参考[“内核/传统编译”](https://wiki.archlinux.org/title/Kernel/Traditional_compilation)
+
+3. 如果使用官方提供的安装镜像,我推荐你使用[ventory](https://www.ventoy.net/)烧录
+
+# 基础安装
+
+## 1.使用arch引导盘
+> 关机,插入U盘后启动,进入bios选择从U盘启动,在第一个选项回车,以进入arch安装环境
+
+## 2.UEFI检查
+
+```bash
+$ systemctl stop reflector.service
+# 禁用自动更新软件源,因为地理上造成的特殊网络环境最好关掉
+```
+
+```bash
+$ ls /sys/firmware/efi/efivars
+# 若输出了一堆efi变量,则启动方式确实为UEFI模式,本帖发布的2025年绝大多数机器是UEFI引导的
+```
+
+## 3. 联网
+
+> archlinux的安装必须要求网络环境,离线安装步骤则更为繁琐,可参考社区的[Offline installation](https://wiki.archlinux.org/title/Offline_installation)
+ 有线网络连接则按下不表,连上网线检查接口提示灯是否闪烁,等待几秒地址分配完成建立连接后即可联网
+ 当然在校园网环境下则需要上级路由完成认证,可以参考[nbtverify](https://github.com/nbtca/nbtverify)项目
+ 无线网络则调用iwctl进行连接
+
+```bash
+$ lspci -k | grep Network
+# 检查无线网卡有没有干活,若明确无问题可以跳过检查
+```
+> 检查内核是否加载了无线网卡驱动
+
+> 一般会显示形如: 00:14.3 Network controller: Intel Corporation Wi-Fi 6 AX201 (rev 20)
+
+> 若没有执行检查无线连接是否被禁用(blocked: yes)
+
+```bash
+$ rfkill list
+# 无线网卡一般叫 wlan0
+```
+
+```bash
+$ ip link set wlan0 up
+# 若有类似报错:Operation not possible due to RF-kill,则执行
+$ rfkill unblock wifi
+```
+```bash
+# 使用iwctl联网
+iwctl # 进入交互式命令行
+device list # 列出无线网卡设备名,比如无线网卡看到叫 wlan0
+station wlan0 scan # 扫描网络
+station wlan0 get-networks # 列出所有 wifi 网络
+station wlan0 connect wifi-name # 进行连接,注意这里无法输入中文。回车后输入密码即可
+exit # 连接成功后退出
+```
+
+## 4. 测试连通性
+```bash
+ping www.bilibili.com # 测试网络连通与否
+```
+> 若在网络配置上有一些意外情况,可以参见[网络配置/无线网络配置](https://wiki.archlinux.org/title/Network_configuration/Wireless)
+
+## 5. 同步系统时钟
+```bash
+$ timedatectl set-ntp true # 将系统时间与网络时间进行同步
+$ timedatectl status # 检查服务状态
+```
+
+## 6. 换国内源(在国际互联网内可忽略)
+```bash
+$ vim /etc/pacman.d/mirrorlist # 准备换源,若上级路由完成代理即可忽略
+Server = https://mirrors.ustc.edu.cn/archlinux/$repo/os/$arch # 中国科学技术大学开源镜像站
+Server = https://mirrors.tuna.tsinghua.edu.cn/archlinux/$repo/os/$arch # 清华大学开源软件镜像站
+Server = https://repo.huaweicloud.com/archlinux/$repo/os/$arch # 华为开源镜像站
+```
+
+## 7. 建立btrfs分区
+```bash
+$ lsblk # 显示当前分区情况
+```
+- 请仔细检查自己要安装arch的目标硬盘名字
+- sda,nvme分别是sata和nvme协议
+- sata协议的硬盘排序为sda、sdb、sdc等,sda1、sda2为分区排序
+- nvme协议的硬盘排序为nvme0n1、nvme1n1、nvme2n1等,nvme0n1p1、nvme0n1p2为分区排序
+- 以sata硬盘为示范,具体硬盘自己更换指令!
+
+```bash
+$ cfdisk /dev/sdx # 对安装 archlinux 的磁盘分区
+```
+> 是不是进入了友好的TUI页面XD
+- 因为已经预先留下了空间给arch,所以应该是有若干个G的FreeSpace
+- 首先创建 Swap 分区。选中 Free space > 再选中操作 [New] > 然后按下回车 Enter 以新建 swap 分区(类似 Windows 的交换文件)
+按下回车后会提示输入 分区大小,Swap 分区建议为电脑内存大小的 60%,或者和内存大小相等 > 然后按下回车 Enter
+- 默认新建的类型是 Linux filesystem,我们需要将类型更改为 Linux swap。选中操作 [Type] > 然后按下回车 Enter > 通过方向键 ↑ 和 ↓ 选中 Linux swap > 最后按下回车 Enter
+- 我们再只需要一个分区即可(因为使用 Btrfs 文件系统,所以根目录和用户主目录在一个分区上),所以类似的:选中 Free space > 再选中操作 [New] > 然后按下回车 Enter 以新建分区
+输入 分区大小(默认是剩余的全部空间。请根据实际情况输入)> 然后按下回车 Enter
+- 分区类型默认即可,无需更改。接下来选中操作 [Write] 并回车 Enter > 输入 yes 并回车 Enter 确认分区操作
+要是没写入就是白忙活,所以确认[Write]了喔
+```
+☢️ 警告
+
+再次提醒!请仔细检查命令和操作的正确性,否则将出现不可预料的情况。最危险的是可能造成数据丢失!
+常见的错误包括不小心把 Windows 的分区删掉了 😥。
+```
+
+```bash
+$ fdisk -l
+# 复查磁盘情况
+```
+
+```bash
+$ mkfs.fat -F32 /dev/sdxn
+# 格式化并创建 Btrfs 子卷
+```
+> 如果你是双系统那么无需格式化,因为linux可以共享Windows的EFI分区,参见[Dual boot with Windows](https://wiki.archlinux.org/title/Dual_boot_with_Windows),只需注意EFI分区大小是否足够你安装
+
+```bash
+$ mkswap /dev/sdxn
+# 格式化 Swap 分区
+```
+
+```bash
+$ mkfs.btrfs -L myArch /dev/sdxn
+# 格式化 Btrfs 分区
+```
+
+```bash
+$ mount -t btrfs -o compress=zstd /dev/sdxn /mnt
+# 挂载分区以创建子卷
+```
+
+```bash
+$ btrfs subvolume create /mnt/@ # 创建 / 目录子卷
+$ btrfs subvolume create /mnt/@home # 创建 /home 目录子卷
+# 创建Btrfs子卷
+```
+
+```bash
+$ umount /mnt
+# 卸载/mnt以挂载子卷
+```
+
+## 8. 挂载,请依序从根目录开始挂载
+
+```bash
+$ mount -t btrfs -o subvol=/@,compress=zstd /dev/sdxn /mnt # 挂载 / 目录
+$ mkdir /mnt/home # 创建 /home 目录
+$ mount -t btrfs -o subvol=/@home,compress=zstd /dev/sdxn /mnt/home # 挂载 /home 目录
+$ mkdir -p /mnt/boot # 创建 /boot 目录
+$ mount /dev/sdxn /mnt/boot # 挂载 /boot 目录
+$ swapon /dev/sdxn # 挂载交换分区
+```
+
+```zsh
+$ df -h # 检查挂载
+$ free -h # 复查Swap分区挂载
+```
+
+## 9. 安装系统
+
+```bash
+$ pacstrap /mnt base base-devel linux linux-firmware btrfs-progs
+# 如果使用btrfs文件系统,额外安装一个btrfs-progs包
+```
+
+```bash
+$ pacman -S archlinux-keyring
+# 如果提示 GPG 证书错误,可能是因为使用的不是最新的镜像文件,可以通过更新 archlinux-keyring 解决此问题
+```
+
+```zsh
+$ pacstrap /mnt networkmanager vim sudo zsh zsh-completions
+# 使用pacstrap脚本安装必要功能性软件
+```
+
+## 10. 生成fstab文件
+> 生成fstab以定义磁盘分区,受当前挂载情况影响
+```zsh
+$ genfstab -U /mnt > /mnt/etc/fstab
+```
+
+## 11. 进入写好的新系统
+```zsh
+$ arch-chroot /mnt
+# 代码高亮消失了?不要慌,说明你已经成功change root了
+```
+
+## 12. 设置主机名称与时区
+```zsh
+$ vim /etc/hostname
+# 给电脑起个名字吧XD(不要包含特殊字符和空格,不然有坑的,并且不起主机名会有时候出奇怪问题,一些GUI程序莫名其妙死了,不论怎样还是起一个名字)
+```
+
+```zsh
+$ vim /etc/hosts
+# 编辑主机host
+```
+
+> 填入如下内容(其中myarch替换成你自己的主机名,中间间隙不是空格是tab对齐,强迫症狂喜XD)
+
+```zsh
+127.0.0.1 localhost
+::1 localhost
+127.0.1.1 myarch.localdomain myarch
+```
+
+```zsh
+$ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+# 在上海时区创建符号链接
+# 别问为什么是上海,因为没有北京XD,当然你也可以使用其他时区
+```
+
+```zsh
+$ ls /usr/share/zoneinfo/
+# 检查你要的时区,更换上条命令的地址
+```
+
+## 13. 硬件时间设置
+
+```zsh
+$ hwclock --systohc
+# 系统时间同步到硬件时间
+```
+
+## 14. 设置区域
+```zsh
+$ vim /etc/locale.gen
+# 编辑 /etc/locale.gen,去掉 en_US.UTF-8 UTF-8 以及 zh_CN.UTF-8 UTF-8 行前的注释符号(#)
+# 这一步决定了软件使用的语言和字符集
+```
+
+```zsh
+$ locale-gen
+# 生成locale
+```
+
+```zsh
+$ echo 'LANG=en_US.UTF-8' > /etc/locale.conf
+# 注入locale.conf,不推荐任何中文locale,tty会乱码
+```
+
+## 15. 设置root密码
+```zsh
+$ passwd root
+# 输入密码是隐式的,并不会显示,并非键盘坏了XD
+```
+
+## 16. 安装微码
+```zsh
+$ pacman -S intel-ucode # Intel
+$ pacman -S amd-ucode #AMD
+```
+
+## 17. 安装Grub引导
+```zsh
+$ pacman -S grub efibootmgr os-prober
+# grub是启动引导器,efibootmgr是被启动器用来向nvram写入启动项,os-prober用于引导win10
+```
+
+```zsh
+$ grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=ARCH
+# 安装grub到EFI分区
+```
+
+```zsh
+$ vim /etc/default/grub
+# 编辑启动参数
+```
+
+```zsh
+# 修改"loglevel=3 quiet" 至 "loglevel=5 nowatchdog"
+# 文件末尾新增一行:GRUB_DISABLE_OS_PROBER=false
+```
+- 去掉 GRUB_CMDLINE_LINUX_DEFAULT 一行中最后的 quiet 参数
+- 把 loglevel 的数值从 3 改成 5。这样是为了后续如果出现系统错误,方便排错
+- 加入 nowatchdog 参数,这可以显著提高开关机速度
+- 加入os-prober参数,用于引导win10
+
+```zsh
+$ grub-mkconfig -o /boot/grub/grub.cfg
+# 生成grub所需配置文件
+```
+
+> 若检查到win10,则会多出一行“Found Windows Boot Manager on /dev/nvme0n1p1@/EFI/Microsoft/Boot/bootmgfw.efi done”的回显,若win10在另一块硬盘则不会输出,进系统后重新挂载再跑一遍即可
+
+
+> 此处的全部参数可参见[archwiki](https://wiki.archlinux.org/title/GRUB)
+
+## 18. 完成安装
+```zsh
+$ exit # 退回安装环境
+$ umount -R /mnt # 卸载新分区
+$ reboot # 重启
+```
+> 重启后用root账户登录
+
+```zsh
+$ systemctl enable --now NetworkManager # 设置开机自启并立即启动 NetworkManager 服务
+$ ping www.bilibili.com # 测试网络连接
+```
+
+> 如果是无线网的话
+```zsh
+$ nmcli dev wifi list # 显示附近的 Wi-Fi 网络
+$ nmcli dev wifi connect "Wi-Fi名(SSID)" password "网络密码" # 连接指定的无线网络
+```
+
+```zsh
+$ nmtui
+# 个人还是比较喜欢nmtui,比较友好XD
+```
+
+```zsh
+$ pacman -S fastfetch
+$ fastfetch
+# 安装fastfetch,检查系统信息
+# 喜闻乐见的neofetch时间XD
+```
+
+```zsh
+$ shutdown 0
+$ shutdown -h now
+$ poweroff
+# 上面三个命令都是关机,🤣记得关机,电源策略还没写呢
+```
+
+---
+
+# 恭喜🎉
+> 至此,你已经完成一个基础无图形界面的archlinux安装了
+
+> 图形化安装应该会在下一次更新发布,不过还是那句老话:多看手册
+
+> 本文抛砖引玉,希望能吸引更多同好前来计协蕉流♂
+
+
+
diff --git a/src/pages/posts/workSummary.md b/src/pages/posts/workSummary.md
index f84e995..e809091 100644
--- a/src/pages/posts/workSummary.md
+++ b/src/pages/posts/workSummary.md
@@ -5,7 +5,7 @@ pubDate: 2025-01-12
description: ' 人类从历史中学到的唯一教训,就是人类无法从历史中学到任何教训'
author: 'zzh0u'
cover:
- url: ./_assets/workSummary/IMG_0069.jpg
+ url: https://oss.nbtca.space/blog/IMG_0069_8xFcDr.jpeg
tags: ["闲聊"]
theme: 'white'
featured: true
diff --git a/src/pages/repair/EventAction.tsx b/src/pages/repair/EventAction.tsx
new file mode 100644
index 0000000..950ee65
--- /dev/null
+++ b/src/pages/repair/EventAction.tsx
@@ -0,0 +1,313 @@
+import type { UserInfoResponse } from "@logto/browser"
+import type { PublicMember } from "../../store/member"
+import { EventStatus, type PublicEvent } from "../../types/event"
+import { saturdayApiBaseUrl } from "../../utils/client"
+import { Button, Form, Select, SelectItem, Textarea } from "@heroui/react"
+import { useEffect, useState } from "react"
+
+export type IdentityContext = {
+ member: PublicMember
+ userInfo: UserInfoResponse
+ token: string
+}
+
+enum RepairRole {
+ repairAdmin = "repair admin",
+ repairMember = "repair member",
+}
+
+export type EventActionProps = {
+ event: PublicEvent
+ identityContext: IdentityContext
+ isLoading?: string
+ onUpdated: (event: PublicEvent) => void
+ onLoading: (loadingAction?: string) => void
+}
+
+const EventSizeOptions: {
+ size: string
+ description?: string
+}[] = [
+ { size: "xs", description: "无需工具,仅简单排查或软件层级操作" },
+ { size: "s", description: "简单拆装部件,操作快,风险低" },
+ { size: "m", description: "需基本工具、一定技术判断,时间较长" },
+ { size: "l", description: "较复杂的拆装和测试流程,需熟练技能、多人协作可能" },
+ { size: "xl", description: "工作量极大,涉及多个设备,需团队作业和详细记录" },
+]
+
+const EventActionCommitForm = (props: {
+ formData: {
+ size: string
+ description: string
+ }
+ setFormData: (data: {
+ size: string
+ description: string
+ }) => void
+}) => {
+ const { formData, setFormData } = props
+ return (
+
+ )
+}
+
+export const EventActionCommit = (props: EventActionProps) => {
+ const [formData, setFormData] = useState({
+ size: "",
+ description: "",
+ })
+
+ useEffect(() => {
+ const description = props.event?.logs.findLast(v => v.action == "commit" || v.action == "alterCommit")?.description
+ setFormData({
+ size: props.event.size || "",
+ description: description || "",
+ })
+ }, [props.event])
+
+ const onSubmit = async () => {
+ props.onLoading("commit")
+ const res = await fetch(`${saturdayApiBaseUrl}/member/events/${props.event.eventId}/commit`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${props.identityContext.token}`,
+ ContentType: "application/json",
+ },
+ body: JSON.stringify({
+ size: formData.size,
+ content: formData.description,
+ }),
+ }).then(res => res.json())
+ props.onLoading()
+ return props.onUpdated(res)
+ }
+ return (
+
+type JsxHandler = (props: EventActionProps) => JSX.Element
+export type EventAction = {
+ action: string
+ label?: string
+ color?: "default" | "primary" | "secondary" | "success" | "warning" | "danger"
+ variant?: "flat" | "solid" | "bordered" | "light" | "faded" | "shadow" | "ghost"
+ handler?: CommonHandler
+ jsxHandler: JsxHandler
+}
+export const getAvailableEventActions = (event: PublicEvent, identityContext: IdentityContext) => {
+ console.log("getting event actions", event, identityContext)
+ const actions: EventAction[] = []
+
+ const makeCommonJsxHandler = (action: Omit) => {
+ return (props: EventActionProps) => {
+ const onAction = async (action: {
+ action: string
+ handler?: CommonHandler
+ }) => {
+ props.onLoading(action.action)
+ if (action.handler) {
+ const res = await action.handler()
+ props.onUpdated(res as PublicEvent)
+ }
+ props.onLoading()
+ }
+ return (
+
+
+
+
diff --git a/src/pages/repair/index.astro b/src/pages/repair/index.astro
index a3e3800..81ecbd9 100644
--- a/src/pages/repair/index.astro
+++ b/src/pages/repair/index.astro
@@ -16,6 +16,9 @@ import RepairHeader from "../../components/header/RepairHeader.astro"
const logtoClient = makeLogtoClient()
const createRepairPath = "/repair/create-ticket"
const authenticated = await logtoClient.isAuthenticated()
+ logtoClient.getIdTokenClaims().then((res) => {
+ console.log(res)
+ })
if (!authenticated) {
window.location.href = `/repair/login-hint?redirectUrl=${createRepairPath}`
return
@@ -65,7 +68,7 @@ import RepairHeader from "../../components/header/RepairHeader.astro"
在接受预约之外,我们每月举办 理工维修日,定时定点提供维修。
+
+
+
+
+ )
+}
+export const EventActionAlterCommit = (props: EventActionProps) => {
+ const [formData, setFormData] = useState({
+ size: "",
+ description: "",
+ })
+ useEffect(() => {
+ const description = props.event?.logs?.findLast(v => v.action == "commit" || v.action == "alterCommit")?.description
+ setFormData({
+ size: props.event.size || "",
+ description: description || "",
+ })
+ }, [props.event])
+
+ const onSubmit = async () => {
+ props.onLoading("alterCommit")
+ const res = await fetch(`${saturdayApiBaseUrl}/member/events/${props.event.eventId}/commit`, {
+ method: "PATCH",
+ headers: {
+ Authorization: `Bearer ${props.identityContext.token}`,
+ ContentType: "application/json",
+ },
+ body: JSON.stringify({
+ size: formData.size,
+ content: formData.description,
+ }),
+ }).then(res => res.json())
+ props.onLoading()
+ return props.onUpdated(res)
+ }
+ return (
+
+
+
+
+
+ )
+}
+
+type CommonHandler = () => Promise
+
+
+ )
+ }
+ }
+
+ if (event.status == EventStatus.open) {
+ actions.push({
+ action: "accept",
+ jsxHandler: makeCommonJsxHandler({
+ action: "accept",
+ label: "接受",
+ variant: "solid",
+ color: "primary",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/member/events/${event.eventId}/accept`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ }
+ else if (event.status == EventStatus.accepted && event.member?.memberId == identityContext.member.memberId) {
+ actions.push({
+ action: "commit",
+ jsxHandler: EventActionCommit,
+ })
+ actions.push({
+ action: "drop",
+ jsxHandler: makeCommonJsxHandler({
+ action: "drop",
+ label: "放弃",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/member/events/${event.eventId}/accept`, {
+ method: "DELETE",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ }
+ else if (event.status == EventStatus.committed) {
+ if (event.member?.memberId == identityContext.member.memberId) {
+ actions.push({
+ action: "alterCommit",
+ jsxHandler: EventActionAlterCommit,
+ })
+ }
+ if (identityContext.userInfo.roles.find(role => role.toLocaleLowerCase() == RepairRole.repairAdmin)) {
+ actions.push({
+ action: "close",
+ jsxHandler: makeCommonJsxHandler({
+ action: "close",
+ color: "success",
+ label: "完成",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/events/${event.eventId}/close`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ actions.push({
+ action: "reject",
+ jsxHandler: makeCommonJsxHandler({
+ action: "rejectCommit",
+ color: "danger",
+ label: "退回",
+ handler: async () => {
+ return await fetch(`${saturdayApiBaseUrl}/events/${event.eventId}/commit`, {
+ method: "DELETE",
+ headers: {
+ Authorization: `Bearer ${identityContext.token}`,
+ },
+ }).then(res => res.json())
+ },
+ }),
+ })
+ }
+ }
+
+ return actions
+}
diff --git a/src/pages/repair/EventDetail.tsx b/src/pages/repair/EventDetail.tsx
index 829f82b..9f4c36c 100644
--- a/src/pages/repair/EventDetail.tsx
+++ b/src/pages/repair/EventDetail.tsx
@@ -1,7 +1,7 @@
-import { useEffect, useState } from "react"
+import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"
import type { components } from "../../types/saturday"
import { saturdayClient } from "../../utils/client"
-import { Textarea, Input, Chip } from "@heroui/react"
+import { Textarea, Input, Chip, Skeleton } from "@heroui/react"
import type { PublicMember } from "../../store/member"
import dayjs from "dayjs"
import { EventStatus, UserEventAction } from "../../types/event"
@@ -15,8 +15,6 @@ function EventLogItem(props: {
}) {
return (
- {/*
- 未开始
+ return 未开始
case EventStatus.accepted:
- return 维修中
+ return 维修中
case EventStatus.committed:
- return 维修中
+ return 待审核
case EventStatus.closed:
- return 已完成
+ return 已完成
case EventStatus.cancelled:
- return 已取消
+ return 已取消
}
}
@@ -93,72 +93,133 @@ const filterEventLog = (event: PublicEvent) => {
}
return filteredLogs
}
-
-export default function EventDetail(props: {
- eventId?: number
-}) {
- const [event, setEvent] = useState()
- const fetchAndSetEvent = async (eventId: number) => {
- const { data } = await saturdayClient.GET("/events/{EventId}", {
- params: {
- path: {
- EventId: eventId,
- },
- },
- })
- setEvent(data)
- }
- useEffect(() => {
- const url = new URL(window.location.href)
- const eventId = props.eventId ?? url.searchParams.get("eventId")
- if (!eventId) {
- return
- }
- fetchAndSetEvent(eventId as unknown as number)
- }, [])
-
- return (
- event
- ? (
-
-
- )
- :
- )
+export type EventDetailRef = {
+ refresh: () => Promise
+ event: PublicEvent | undefined
}
+const EventDetail = forwardRef void
+ action?: React.ReactNode
+ children?: (event: PublicEvent) => React.ReactNode
+}>((props, ref) => {
+ const [event, setEvent] = useState()
+
+ const fetchAndSetEvent = async (eventId: number) => {
+ const { data } = await saturdayClient.GET("/events/{EventId}", {
+ params: {
+ path: {
+ EventId: eventId,
+ },
+ },
+ })
+ setEvent(data)
+ return data
+ }
+
+ const refresh = async () => {
+ const url = new URL(window.location.href)
+ const eventId = props.eventId ?? url.searchParams.get("eventId")
+ console.log("refresh eventId", eventId)
+ if (eventId) {
+ return await fetchAndSetEvent(eventId as unknown as number)
+ }
+ }
+
+ const repairDescription = useMemo(() => {
+ return event?.logs.findLast(v => v.action == "commit" || v.action == "alterCommit")?.description
+ }, [event])
+
+ // 初次加载
+ useEffect(() => {
+ refresh()
+ }, [])
+
+ // 暴露给父组件的方法
+ useImperativeHandle(ref, () => ({
+ refresh,
+ event,
+ }))
+
+ return (
+ event
+ ? (
+
+
+ )
+ : (
+
+
+ 导出为Excel
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx
new file mode 100644
index 0000000..b8f9d00
--- /dev/null
+++ b/src/pages/repair/RepairAdmin.tsx
@@ -0,0 +1,453 @@
+import {
+ Table,
+ TableHeader,
+ TableColumn,
+ TableBody,
+ TableRow,
+ TableCell,
+ User,
+ Pagination,
+ Spinner,
+ Popover,
+ PopoverTrigger,
+ PopoverContent,
+ Button,
+ CheckboxGroup,
+ Checkbox,
+ Drawer,
+ DrawerContent,
+ DrawerHeader,
+ DrawerBody,
+ DrawerFooter,
+ useDisclosure,
+ Chip,
+} from "@heroui/react"
+import { useCallback, useEffect, useMemo, useRef, useState } from "react"
+import { useAsyncList } from "@react-stately/data"
+import type { components } from "../../types/saturday"
+import { saturdayApiBaseUrl, saturdayClient } from "../../utils/client"
+import EventDetail, { EventStatusChip, type EventDetailRef } from "./EventDetail"
+import dayjs from "dayjs"
+import { EventStatus, UserEventStatus } 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"
+
+type PublicEvent = components["schemas"]["PublicEvent"]
+
+export const EyeIcon = (props) => {
+ return (
+
+ )
+}
+
+function CheckboxPopover(props: {
+ value: string[]
+ onValueChange: (value: string[]) => void
+}) {
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+function TicketDetailDrawer(props: {
+ event: PublicEvent
+ identity: IdentityContext
+ isOpen: boolean
+ onEventUpdated: (event: PublicEvent) => void
+ onOpenChange: (isOpen: boolean) => void
+ onClose: () => void
+ onDelete: () => void
+ onEdit: () => void
+}) {
+ const { isOpen, onOpenChange, onClose } = props
+ const [isLoading, setIsLoading] = useState("")
+
+ const eventDetailRef = useRef(null)
+
+ const [availableActions, setAvailableActions] = useState([])
+
+ useEffect(() => {
+ if (!props.event || !props.identity?.member || !props.identity?.userInfo?.roles) {
+ return
+ }
+ setAvailableActions(getAvailableEventActions(props.event, props.identity))
+ }, [props.event, props.identity])
+
+ const onEventUpdated = async (event: PublicEvent) => {
+ props.onEventUpdated(event)
+ const res = await eventDetailRef.current?.refresh()
+ console.log("onEventUpdated", res)
+ if (!res || !props.identity?.member || !props.identity?.userInfo?.roles) {
+ return
+ }
+ setAvailableActions(getAvailableEventActions(res, props.identity))
+ }
+
+ return (
+
+
+
+
+
+
+ {
+ event => (
+
+
+
+
+
+
+
+ )
+}
+
+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)
+ const rowsPerPage = 10
+ const [statusFilter, setStatusFilter] = useState(
+ UserEventStatus.filter(v => v.status !== EventStatus.cancelled).map(v => v.status),
+ )
+ const { isOpen, onOpen, onOpenChange } = useDisclosure()
+ const [userInfo, setUserInfo] = useState()
+ const [currentMember, setCurrentMember] = useState()
+ const [token, setToken] = useState()
+
+ useEffect(() => {
+ const check = async () => {
+ const adminPath = "/repair/admin"
+ const authenticated = await makeLogtoClient().isAuthenticated()
+ if (!authenticated) {
+ window.location.href = `/repair/login-hint?redirectUrl=${adminPath}`
+ return
+ }
+ const res = await makeLogtoClient().getIdTokenClaims()
+ 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 currentMember = await fetch(`${saturdayApiBaseUrl}/member`, {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }).then(res => res.json())
+ setCurrentMember(currentMember)
+ }
+ check()
+ }, [])
+
+ const list = useAsyncList({
+ async load() {
+ const { data } = await saturdayClient.GET("/events", {
+ params: {
+ query: {
+ order: "DESC",
+ offset: 0,
+ limit: 1000,
+ },
+ },
+ })
+
+ setIsLoading(false)
+
+ return {
+ items: data,
+ }
+ },
+ async sort({ items, sortDescriptor }) {
+ return {
+ items: items.sort((a, b) => {
+ const first = a[sortDescriptor.column]
+ const second = b[sortDescriptor.column]
+ let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1
+
+ if (sortDescriptor.direction === "descending") {
+ cmp *= -1
+ }
+
+ return cmp
+ }),
+ }
+ },
+ })
+
+ const filteredList = useMemo(() => {
+ if (statusFilter.length > 0) {
+ return list.items.filter(item => statusFilter.includes(item.status))
+ }
+ return list.items
+ }, [list, statusFilter])
+
+ const items = useMemo(() => {
+ const start = (page - 1) * rowsPerPage
+ const end = start + rowsPerPage
+
+ return filteredList.slice(start, end)
+ }, [filteredList, page, rowsPerPage])
+
+ const pages = useMemo(() => {
+ return Math.ceil(filteredList.length / rowsPerPage)
+ }, [filteredList, rowsPerPage])
+
+ useEffect(() => {
+ setPage(1)
+ }, [statusFilter])
+
+ const columns: {
+ key: string
+ label: string
+ allowSorting?: boolean
+ content?: JSX.Element
+ }[] = [
+ {
+ key: "eventId",
+ label: "单号",
+ },
+ {
+ key: "problem",
+ label: "问题描述",
+ },
+ {
+ key: "model",
+ label: "型号",
+ },
+ {
+ key: "size",
+ label: "工作量",
+ },
+ {
+ key: "memberId",
+ label: "处理人",
+ },
+ {
+ key: "gmtCreate",
+ label: "创建时间",
+ },
+ {
+ key: "status",
+ label: "状态",
+ content: (
+ ()
+ const onOpenEventDetail = (event: PublicEvent) => {
+ setActiveEvent(event)
+ onOpen()
+ }
+
+ const renderCell = useCallback((event: PublicEvent, columnKey: string | number) => {
+ const cellValue = event[columnKey]
+
+ switch (columnKey) {
+ case "problem":
+ return (
+
+ {event.member.alias}
+
+ )
+ : <>>
+ )
+ case "size":
+ return (
+ cellValue ? {"size:" + cellValue} : <>>
+ )
+ case "gmtCreate":
+ return (
+
+ {dayjs(cellValue).format("YYYY-MM-DD HH:mm")}
+
+ )
+ case "status":
+ return EventStatusChip({
+ status: cellValue,
+ size: "sm",
+ })
+ case "actions":
+ return (
+
+ )
+
+ default:
+ return cellValue
+ }
+ }, [])
+ return (
+
+ {
+ onOpenChange()
+ }}
+ onDelete={() => {}}
+ onEdit={() => {}}
+ >
+
+
+ )
+}
diff --git a/src/pages/repair/TicketForm.tsx b/src/pages/repair/TicketForm.tsx
index 038d05e..9c01c18 100644
--- a/src/pages/repair/TicketForm.tsx
+++ b/src/pages/repair/TicketForm.tsx
@@ -181,8 +181,9 @@ function TicketFormCreated(props: {
-
-
-
*/}
@@ -37,7 +35,7 @@ function EventLogItem(props: {
+
@@ -46,20 +44,22 @@ function EventLogItem(props: {
)
}
-function EventStatusChip(props: {
+export function EventStatusChip(props: {
+ size?: "sm" | "md" | "lg"
status: string
}) {
+ const size = props.size || "md"
switch (props.status) {
case EventStatus.open:
- return
{dayjs(props.eventLog.gmtCreate).format("YYYY-MM-DD HH:mm")}
-
- 维修详情
-
-
- #{event.eventId}
-
-
-
-
-
-
-
-
-
-
- )
- })
- }
-
-
- 维修记录
-
- {
- filterEventLog(event).map((v, index) => {
- return (
-
+
+ 维修详情
+
+
+ #{event.eventId}
+
+
+ {
+ event.size
+ ? {"size:" + event.size}
+ : <>>
+ }
+
+
+
+
+
+ {
+ repairDescription
+ ? (
+
+ )
+ : <>>
+ }
+
+
+
+ )
+ })
+ }
+
+
+ 维修记录
+
+ {
+ filterEventLog(event).map((v, index) => {
+ return (
+
+ {props.children(event)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ )
+ })
+
+export default EventDetail
diff --git a/src/pages/repair/ExportEventDialog.tsx b/src/pages/repair/ExportEventDialog.tsx
new file mode 100644
index 0000000..5c0d59b
--- /dev/null
+++ b/src/pages/repair/ExportEventDialog.tsx
@@ -0,0 +1,108 @@
+import { useState } from "react"
+import {
+ Modal,
+ ModalContent,
+ ModalHeader,
+ ModalBody,
+ ModalFooter,
+ Button,
+ DateRangePicker,
+} from "@heroui/react"
+import { parseDate } from "@internationalized/date"
+import { saturdayApiBaseUrl } from "../../utils/client"
+import { makeLogtoClient } from "../../utils/auth"
+import dayjs from "dayjs"
+
+export function ExportExcelModal() {
+ const [isOpen, setIsOpen] = useState(false)
+ const [dateRange, setDateRange] = useState({
+ start: parseDate(dayjs().subtract(1, "month").format("YYYY-MM-DD")),
+ end: parseDate(dayjs().format("YYYY-MM-DD")),
+ })
+ const [loading, setLoading] = useState(false)
+
+ const openModal = () => setIsOpen(true)
+ const closeModal = () => setIsOpen(false)
+
+ const downloadExcel = async () => {
+ if (!dateRange.start || !dateRange.end) return
+
+ setLoading(true)
+ try {
+ const start = dateRange.start.toString() // Format: 'YYYY-MM-DD'
+ const end = dateRange.end.toString()
+ const url = `${saturdayApiBaseUrl}/events/xlsx?start_time=${start}&end_time=${end}`
+
+ const token = await makeLogtoClient().getAccessToken()
+ const response = await fetch(url, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ })
+ if (!response.ok) throw new Error("Download failed")
+
+ // Extract filename from Content-Disposition header
+ const disposition = response.headers.get("Content-Disposition")
+ let filename = "export.xlsx" // Default filename
+ if (disposition && disposition.includes("filename=")) {
+ const filenameMatch = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
+ if (filenameMatch != null && filenameMatch[1]) {
+ filename = filenameMatch[1].replace(/['"]/g, "")
+ }
+ }
+
+ const blob = await response.blob()
+ const downloadUrl = window.URL.createObjectURL(blob)
+ const link = document.createElement("a")
+ link.href = downloadUrl
+ link.setAttribute("download", filename)
+ document.body.appendChild(link)
+ link.click()
+ link.remove()
+ window.URL.revokeObjectURL(downloadUrl) // Clean up
+ }
+ catch (error) {
+ alert("Failed to download Excel file: " + error.message)
+ }
+ finally {
+ setLoading(false)
+ closeModal()
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+ {
+ UserEventStatus.map((status) => {
+ return (
+
+
+
+ )
+ })
+ }
+
+
+ 维修详情
+ {isLoading} +
+ {
+ availableActions?.map((action) => {
+ return (
+ {
+ setIsLoading(action)
+ }}
+ >
+
+ )
+ }) || <>>
+ }
+
+ )
+ }
+
+ 状态
+
+
+ ),
+ },
+ {
+ key: "actions",
+ label: "操作",
+ },
+ ]
+
+ const [activeEvent, setActiveEvent] = useState
+ {cellValue}
+
+ )
+ case "memberId":
+ return (
+ event.member
+ ? (
+
+
+ : <>>
+ }
+
+ 维修管理
+ {
+ userInfo?.roles?.find(v => v.toLowerCase() == "repair admin")
+ ?
+
+
+
+
+
+
)
}
diff --git a/src/pages/repair/admin.astro b/src/pages/repair/admin.astro
new file mode 100644
index 0000000..20a5e04
--- /dev/null
+++ b/src/pages/repair/admin.astro
@@ -0,0 +1,12 @@
+---
+import BaseLayout from "../../layouts/BaseLayout.astro"
+import RepairHeader from "../../components/header/RepairHeader.astro"
+import RepairAdmin from "./RepairAdmin"
+---
+
+
+
+
+
+
diff --git a/src/styles/global.css b/src/styles/global.css
index d1170cf..e05d037 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -7709,16 +7709,16 @@ html.no-touch.no-reduced-motion
}
.footer-main .item .logo {
- font-size: 16px;
+ /* font-size: 16px; */
font-weight: 600;
color: var(--black);
}
.footer-main .foot-nav-items {
- display: flex;
+ /* display: flex;
flex-direction: row;
justify-content: space-between;
- align-items: flex-start;
+ align-items: flex-start; */
}
@media screen and (min-width: 320px) {
@@ -7774,10 +7774,10 @@ html.no-touch.no-reduced-motion
}
.footer-main .foot-nav-items {
- display: flex;
+ /* display: flex;
flex-direction: row;
justify-content: space-between;
- align-items: flex-start;
+ align-items: flex-start; */
}
@media screen and (min-width: 320px) {
@@ -7805,7 +7805,7 @@ html.no-touch.no-reduced-motion
.footer-main .item div,
.footer-main .item a {
- padding: 8px 5px;
+ padding: 4px 0px;
font-size: 14px;
color: var(--footer-pipe-color);
}
@@ -7816,7 +7816,7 @@ html.no-touch.no-reduced-motion
.footer-main .foot-nav-items .item-title {
color: var(--footer-directory-title-color);
- font-size: 14px;
+ /* font-size: 14px; */
font-weight: 600;
}
diff --git a/src/types/event.ts b/src/types/event.ts
index 5f17fea..5b31042 100644
--- a/src/types/event.ts
+++ b/src/types/event.ts
@@ -1,3 +1,5 @@
+import type { components } from "./saturday"
+
export interface Status {
status: string
text: string
@@ -76,3 +78,5 @@ export const UserEventAction: Action[] = [
icon: "status_cancelled.svg",
},
]
+
+export type PublicEvent = components["schemas"]["PublicEvent"]
diff --git a/src/utils/client.ts b/src/utils/client.ts
index 858f920..f6c8919 100644
--- a/src/utils/client.ts
+++ b/src/utils/client.ts
@@ -2,8 +2,10 @@ import createClient from "openapi-fetch"
import type { paths as saturdayPaths } from "../types/saturday"
import { ApiClient } from "./active"
+export const saturdayApiBaseUrl = import.meta.env.PROD ? "https://api.nbtca.space/v2" : "/saturday"
+
export const saturdayClient = createClient({
- baseUrl: import.meta.env.PROD ? "https://api.nbtca.space/v2/" : "/saturday",
+ baseUrl: saturdayApiBaseUrl,
})
export const activeClient = new ApiClient({