diff --git a/app/(dashboard)/dashboard/shop/page.tsx b/app/(dashboard)/dashboard/shop/page.tsx index 8df8019..87d22aa 100644 --- a/app/(dashboard)/dashboard/shop/page.tsx +++ b/app/(dashboard)/dashboard/shop/page.tsx @@ -7,50 +7,99 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Separator } from "@/components/ui/separator" import { Textarea } from "@/components/ui/textarea" -import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import { addShopAnnouncement, deleteShopAnnouncement, updateShop } from "@/lib/api" +import { toApiError } from "@/lib/errors" +import { useMyShop } from "@/lib/hooks/use-my-shop" +import { notifyInfo, notifySuccess } from "@/lib/toast" import type { Shop } from "@/lib/types" -import { useAuthStore } from "@/store/auth" -import { useShopStore } from "@/store/shops" import { DollarSign, Edit, ExternalLink, ListOrdered, Star, Users } from "lucide-react" import Link from "next/link" import { useState } from "react" export default function ShopManagementPage() { - const userId = useAuthStore((state) => state.user?.id) - const shops = useShopStore((state) => state.shops) - const shop = resolveOwnerShop(userId, shops) - const updateShop = useShopStore((state) => state.updateShop) - const updateAnnouncement = useShopStore((state) => state.updateAnnouncement) - const addAnnouncement = useShopStore((state) => state.addAnnouncement) + const { shop, setShop, loading, error, refreshShop } = useMyShop() + + if (loading) { + return
加载中...
+ } + + if (error) { + return
{error}
+ } if (!shop) { return
当前账号没有可管理的店铺
} return ( - + ) } function ShopManagementContent({ shop, - updateShop, - updateAnnouncement, - addAnnouncement, + setShop, + refreshShop, }: { shop: Shop - updateShop: (shopId: string, patch: Partial>) => void - updateAnnouncement: (shopId: string, index: number, announcement: string) => void - addAnnouncement: (shopId: string, announcement: string) => void + setShop: (shop: Shop | null) => void + refreshShop: () => Promise }) { const [name, setName] = useState(shop.name) const [description, setDescription] = useState(shop.description) + const [saving, setSaving] = useState(false) + + const handleSave = async () => { + setSaving(true) + try { + const nextShop = await updateShop(shop.id, { + name, + description, + commissionType: shop.commissionType, + commissionValue: shop.commissionValue, + allowMultiShop: shop.allowMultiShop, + allowIndependentOrders: shop.allowIndependentOrders, + dispatchMode: shop.dispatchMode, + }) + setShop(nextShop) + notifySuccess("店铺信息已保存") + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setSaving(false) + } + } + + const handleAddAnnouncement = async () => { + const next = window.prompt("", "") + if (next === null) return + const value = next.trim() + if (!value) return + + setSaving(true) + try { + await addShopAnnouncement(shop.id, value) + await refreshShop() + notifySuccess("公告已添加") + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setSaving(false) + } + } + + const handleDeleteAnnouncement = async (index: number) => { + setSaving(true) + try { + await deleteShopAnnouncement(shop.id, index) + await refreshShop() + notifySuccess("公告已删除") + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setSaving(false) + } + } return (
@@ -126,12 +175,10 @@ function ShopManagementContent({ />
))} @@ -172,12 +214,9 @@ function ShopManagementContent({ variant="outline" size="sm" onClick={() => { - const next = window.prompt("", "") - if (next === null) return - const value = next.trim() - if (!value) return - addAnnouncement(shop.id, value) + void handleAddAnnouncement() }} + disabled={saving} > 添加公告 diff --git a/app/(dashboard)/dashboard/shop/rules/page.tsx b/app/(dashboard)/dashboard/shop/rules/page.tsx index f524792..d5c7c37 100644 --- a/app/(dashboard)/dashboard/shop/rules/page.tsx +++ b/app/(dashboard)/dashboard/shop/rules/page.tsx @@ -12,54 +12,71 @@ import { SelectValue, } from "@/components/ui/select" import { Switch } from "@/components/ui/switch" -import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import { updateShop } from "@/lib/api" +import { toApiError } from "@/lib/errors" +import { useMyShop } from "@/lib/hooks/use-my-shop" +import { notifyInfo, notifySuccess } from "@/lib/toast" import type { Shop } from "@/lib/types" -import { useAuthStore } from "@/store/auth" -import { useShopStore } from "@/store/shops" import { Save } from "lucide-react" import { useState } from "react" export default function ShopRulesPage() { - const userId = useAuthStore((state) => state.user?.id) - const shops = useShopStore((state) => state.shops) - const shop = resolveOwnerShop(userId, shops) - const updateShop = useShopStore((state) => state.updateShop) + const { shop, setShop, loading, error } = useMyShop() + + if (loading) { + return
加载中...
+ } + + if (error) { + return
{error}
+ } if (!shop) { return
当前账号没有可管理的店铺
} - return + return } -function ShopRulesForm({ - shop, - updateShop, -}: { - shop: Shop - updateShop: (shopId: string, patch: Partial>) => void -}) { +function ShopRulesForm({ shop, setShop }: { shop: Shop; setShop: (shop: Shop | null) => void }) { const [commissionType, setCommissionType] = useState(shop.commissionType) const [commissionValue, setCommissionValue] = useState(shop.commissionValue) const [allowMultiShop, setAllowMultiShop] = useState(shop.allowMultiShop) const [allowIndependentOrders, setAllowIndependentOrders] = useState(shop.allowIndependentOrders) const [dispatchMode, setDispatchMode] = useState(shop.dispatchMode) + const [saving, setSaving] = useState(false) - const handleSave = () => { - updateShop(shop.id, { - commissionType, - commissionValue, - allowMultiShop, - allowIndependentOrders, - dispatchMode, - }) + const handleSave = async () => { + setSaving(true) + try { + const nextShop = await updateShop(shop.id, { + name: shop.name, + description: shop.description, + commissionType, + commissionValue, + allowMultiShop, + allowIndependentOrders, + dispatchMode, + }) + setShop(nextShop) + notifySuccess("规则已保存") + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setSaving(false) + } } return (

规则设置

- diff --git a/app/(dashboard)/dashboard/shop/templates/page.tsx b/app/(dashboard)/dashboard/shop/templates/page.tsx index c5fc836..c6a2173 100644 --- a/app/(dashboard)/dashboard/shop/templates/page.tsx +++ b/app/(dashboard)/dashboard/shop/templates/page.tsx @@ -3,10 +3,12 @@ import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Switch } from "@/components/ui/switch" -import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" +import { updateShopTemplate } from "@/lib/api" +import { getShopSections } from "@/lib/domain/shop-template" +import { toApiError } from "@/lib/errors" +import { useMyShop } from "@/lib/hooks/use-my-shop" +import { notifyInfo } from "@/lib/toast" import type { Shop, ShopSection } from "@/lib/types" -import { useAuthStore } from "@/store/auth" -import { useShopStore } from "@/store/shops" import { Eye, EyeOff, GripVertical } from "lucide-react" import Link from "next/link" import { type DragEvent, useEffect, useState } from "react" @@ -30,37 +32,35 @@ const sectionDescriptions: Record = { } export default function ShopTemplatesPage() { - const userId = useAuthStore((state) => state.user?.id) - const shops = useShopStore((state) => state.shops) - const shop = resolveOwnerShop(userId, shops) - const updateTemplateSections = useShopStore((state) => state.updateTemplateSections) + const { shop, setShop, loading, error } = useMyShop() + + if (loading) { + return
加载中...
+ } + + if (error) { + return
{error}
+ } if (!shop) { return
当前账号没有可管理的店铺
} - return ( - - ) + return } function ShopTemplatesEditor({ shop, - updateTemplateSections, + setShop, }: { shop: Shop - updateTemplateSections: (shopId: string, sections: ShopSection[]) => void + setShop: (shop: Shop | null) => void }) { - const [sections, setSections] = useState( - [...shop.templateConfig.sections].sort((a, b) => a.order - b.order), - ) + const [sections, setSections] = useState(getShopSections(shop)) const [dragIndex, setDragIndex] = useState(null) const [dropIndex, setDropIndex] = useState(null) const [showSavedToast, setShowSavedToast] = useState(false) + const [saving, setSaving] = useState(false) useEffect(() => { if (!showSavedToast) { @@ -116,9 +116,21 @@ function ShopTemplatesEditor({ setDropIndex(null) } - const handleSaveTemplate = () => { - updateTemplateSections(shop.id, sections) - setShowSavedToast(true) + const handleSaveTemplate = async () => { + setSaving(true) + try { + const templateConfig = { + ...shop.templateConfig, + sections, + } + await updateShopTemplate(shop.id, templateConfig) + setShop({ ...shop, templateConfig }) + setShowSavedToast(true) + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setSaving(false) + } } return ( @@ -137,7 +149,14 @@ function ShopTemplatesEditor({ - +
diff --git a/lib/hooks/use-my-shop.ts b/lib/hooks/use-my-shop.ts new file mode 100644 index 0000000..1604fc3 --- /dev/null +++ b/lib/hooks/use-my-shop.ts @@ -0,0 +1,62 @@ +"use client" + +import { getMyShop } from "@/lib/api/shops" +import { toApiError } from "@/lib/errors" +import type { Shop } from "@/lib/types" +import { useCallback, useEffect, useState } from "react" + +export function useMyShop() { + const [shop, setShop] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const refreshShop = useCallback(async () => { + setLoading(true) + setError(null) + + try { + const nextShop = (await getMyShop()) ?? null + setShop(nextShop) + return nextShop + } catch (error) { + setShop(null) + setError( + error instanceof Error && error.message === "UNAUTHORIZED" + ? "请先登录" + : toApiError(error).msg, + ) + return null + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + let cancelled = false + + getMyShop() + .then((nextShop) => { + if (cancelled) return + setShop(nextShop ?? null) + }) + .catch((error) => { + if (cancelled) return + setShop(null) + setError( + error instanceof Error && error.message === "UNAUTHORIZED" + ? "请先登录" + : toApiError(error).msg, + ) + }) + .finally(() => { + if (cancelled) return + setLoading(false) + }) + + return () => { + cancelled = true + } + }, []) + + return { shop, setShop, loading, error, refreshShop } +}