import { Badge, Box, Button, Checkbox, FormControl, FormHelperText, FormLabel, HStack, Input, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Table, Tbody, Td, Text, Th, Thead, Tr, VStack, createDisclosure, } from "@hope-ui/solid" import copy from "copy-to-clipboard" import { createSignal, For, onMount, Show } from "solid-js" import { DeletePopover } from "./common/DeletePopover" import { useManageTitle, useT } from "~/hooks" import { ShareItem } from "~/types" import { deleteShare, disableShare, getShareList, updateShare, } from "~/utils/api" import { dateTimeLocalToISOString, formatDate, handleResp, notify, toDateTimeLocalValue, } from "~/utils" const Shares = () => { const t = useT() useManageTitle("manage.sidemenu.shares") const { isOpen, onOpen, onClose } = createDisclosure() const [shares, setShares] = createSignal([]) const [loading, setLoading] = createSignal(false) const [deleting, setDeleting] = createSignal(null) const [disabling, setDisabling] = createSignal(null) const [updating, setUpdating] = createSignal(false) const [editingShare, setEditingShare] = createSignal(null) const [editShareId, setEditShareId] = createSignal("") const [editName, setEditName] = createSignal("") const [editPassword, setEditPassword] = createSignal("") const [editExpireAt, setEditExpireAt] = createSignal("") const [editAccessLimit, setEditAccessLimit] = createSignal("0") const [editAllowPreview, setEditAllowPreview] = createSignal(true) const [editAllowDownload, setEditAllowDownload] = createSignal(true) const refresh = async () => { setLoading(true) const resp = await getShareList() handleResp(resp, (data) => { setShares(data.content) }) setLoading(false) } const remove = async (shareId: string) => { setDeleting(shareId) const resp = await deleteShare(shareId) handleResp(resp, async () => { notify.success(t("global.delete_success")) await refresh() }) setDeleting(null) } const invalidate = async (shareId: string) => { setDisabling(shareId) const resp = await disableShare(shareId) handleResp(resp, async () => { notify.success(t("share.disabled", undefined, "Disabled")) await refresh() }) setDisabling(null) } const closeEditor = () => { onClose() setEditingShare(null) setEditShareId("") setEditName("") setEditPassword("") setEditExpireAt("") setEditAccessLimit("0") setEditAllowPreview(true) setEditAllowDownload(true) setUpdating(false) } const openEditor = (item: ShareItem) => { setEditingShare(item) setEditShareId(item.share_id) setEditName(item.name) setEditPassword("") setEditExpireAt(toDateTimeLocalValue(item.expires_at)) setEditAccessLimit(String(item.access_limit || 0)) setEditAllowPreview(item.allow_preview) setEditAllowDownload(item.allow_download) onOpen() } const save = async () => { const item = editingShare() if (!item) return const nextShareId = editShareId().trim() if (!nextShareId) { notify.error(t("share.invalid_link", undefined, "Link cannot be empty")) return } const accessLimitValue = Number.parseInt(editAccessLimit(), 10) if (Number.isNaN(accessLimitValue) || accessLimitValue < 0) { notify.error( t( "share.invalid_access_limit", undefined, "Access limit must be 0 or greater", ), ) return } const expireAtValue = editExpireAt().trim() const expireAtISO = expireAtValue ? dateTimeLocalToISOString(expireAtValue) : "" if (expireAtValue && !expireAtISO) { notify.error( t("share.invalid_expire", undefined, "Expire time format is invalid"), ) return } setUpdating(true) const resp = await updateShare({ share_id: item.share_id, new_share_id: nextShareId, name: editName(), password: editPassword().trim() || undefined, expire_at: expireAtISO, access_limit: accessLimitValue, allow_preview: editAllowPreview(), allow_download: editAllowDownload(), }) handleResp(resp, async () => { notify.success(t("global.save_success", undefined, "Saved")) closeEditor() await refresh() }) setUpdating(false) } const isExpired = (item: ShareItem) => Boolean( item.expires_at && new Date(item.expires_at).getTime() <= Date.now(), ) onMount(() => { refresh() }) return ( {(item) => ( )}
{t("global.name")} {t("share.link", undefined, "Link")} {t("share.target", undefined, "Target")} {t("share.mode", undefined, "Mode")} {t("share.expire", undefined, "Expire")} {t("share.stats", undefined, "Stats")} {t("global.operations")}
{item.name} {item.share_id} {item.root_path} {t("share.password_required", undefined, "Password")} {t( "share.burn_after_read", undefined, "Burn after read", )} 1}> {t( "share.access_limit_badge", { count: item.access_limit, }, `${item.access_limit} accesses`, )} {t( "share.preview_disabled", undefined, "Preview off", )} {t( "share.download_disabled", undefined, "Download off", )} {t("share.standard", undefined, "Standard")} {item.expires_at ? formatDate(item.expires_at) : t("share.never", undefined, "Never")} 0}> {t("share.access", undefined, "Access")}:{" "} {item.access_count}/{item.access_limit} 0}> {" ยท "} {t( "share.remaining_accesses", { count: item.remaining_accesses, }, `${item.remaining_accesses} left`, )} {t("share.consumed", undefined, "Consumed")}:{" "} {formatDate(item.consumed_at!)} {`${item.view_count} / ${item.download_count}`} {item.consumed_at ? t("share.consumed", undefined, "Consumed") : isExpired(item) ? t("share.expired", undefined, "Expired") : item.enabled ? t("share.active", undefined, "Active") : t("share.disabled", undefined, "Disabled")} {t("share.last_access", undefined, "Last access")}:{" "} {formatDate(item.last_access_at!)} remove(item.share_id)} />
{t("global.edit", undefined, "Edit")} {t("share.target", undefined, "Target")} {t("share.link", undefined, "Link")} setEditShareId(e.currentTarget.value)} /> {t( "share.link_hint", undefined, "Use letters, numbers, underscore or hyphen only.", )} {t("share.name", undefined, "Share name")} setEditName(e.currentTarget.value)} /> {t("share.password", undefined, "Password")} setEditPassword(e.currentTarget.value)} /> {t( "share.password_keep_hint", undefined, "Leave empty to keep the current password.", )} {t("share.access_limit", undefined, "Access limit")} setEditAccessLimit(e.currentTarget.value)} /> {t( "share.access_limit_hint", undefined, "Use 0 for unlimited, 1 for burn after read.", )} {t("share.expire", undefined, "Expire")} setEditExpireAt(e.currentTarget.value)} /> {t( "share.expire_hint", undefined, "Leave empty for never expire.", )} setEditAllowPreview(!editAllowPreview())} > {t("share.allow_preview", undefined, "Allow preview")} setEditAllowDownload(!editAllowDownload())} > {t("share.allow_download", undefined, "Allow download")}
) } export default Shares