Alternative web application for the pdsadmin command
at main 94 lines 2.9 kB view raw
1import type { MouseEvent } from "react"; 2import { useState } from "react"; 3import { useTranslation } from "react-i18next"; 4 5import { useOpenModal } from "../../atoms/modal"; 6import { usePDS, usePDSHostname } from "../../atoms/pds"; 7import { cn } from "../../utils/cn"; 8 9type PDSButtonProps = { 10 type: "request-crawl" | "logout" | "create-account"; 11 iconClassName: string; 12 children: string; 13}; 14 15function PDSButton({ type, iconClassName, children }: PDSButtonProps) { 16 const openModal = useOpenModal(); 17 return ( 18 <button 19 className="btn btn-ghost h-12 shadow" 20 onClick={() => openModal({ type })} 21 data-testid={`${type}-button`} 22 > 23 <span className={cn("size-4", iconClassName)}></span> 24 <span className="truncate">{children}</span> 25 </button> 26 ); 27} 28 29function CreateInviteCodeButton() { 30 const pds = usePDS(); 31 const openModal = useOpenModal(); 32 const [loading, setLoading] = useState(false); 33 const { t } = useTranslation(); 34 35 const handleCreateInviteCode = async (e: MouseEvent) => { 36 e.stopPropagation(); 37 setLoading(true); 38 try { 39 const code = await pds.createInviteCode(); 40 openModal({ type: "invite-code", code }); 41 } catch (error) { 42 alert(t("pds-menu.alerts.error", { message: String(error) })); 43 } finally { 44 setLoading(false); 45 } 46 }; 47 48 return ( 49 <div 50 // button要素を使うとドロップダウンが閉じてしまうのでdivを使う 51 role="button" 52 className={cn("btn btn-ghost h-12 shadow", { 53 "pointer-events-none": loading, 54 "pointer-events-auto": !loading, 55 })} 56 onClick={handleCreateInviteCode} 57 data-testid="create-invite-code-button" 58 > 59 {loading && ( 60 <div className="loading loading-spinner loading-sm absolute"></div> 61 )} 62 <span className="i-lucide-ticket size-4"></span> 63 <span className={cn("truncate", loading && "opacity-0")}> 64 {t("pds-menu.buttons.create-invite-code")} 65 </span> 66 </div> 67 ); 68} 69 70export function PDSMenu() { 71 const pdsHostname = usePDSHostname(); 72 const { t } = useTranslation(); 73 74 return ( 75 <div className="card rounded-box bg-base-100 p-2 shadow-md"> 76 <div className="flex justify-center py-4"> 77 PDS: 78 <span className="ml-2 font-bold">{pdsHostname}</span> 79 </div> 80 <div className="grid grid-cols-2 grid-rows-2 gap-2"> 81 <PDSButton type="create-account" iconClassName="i-lucide-user-plus"> 82 {t("pds-menu.buttons.create-account")} 83 </PDSButton> 84 <CreateInviteCodeButton /> 85 <PDSButton type="request-crawl" iconClassName="i-lucide-refresh-cw"> 86 {t("pds-menu.buttons.request-crawl")} 87 </PDSButton> 88 <PDSButton type="logout" iconClassName="i-lucide-log-out"> 89 {t("pds-menu.buttons.sign-out")} 90 </PDSButton> 91 </div> 92 </div> 93 ); 94}