添加优惠券发放的功能 & 切换后台线上和线下首页区别
This commit is contained in:
@@ -39,9 +39,20 @@ export async function deleteCoupon(id: number) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function issueCoupon(data: {
|
export async function getReleaseCoupon(data: {
|
||||||
coupon_id: number
|
coupon_id: number
|
||||||
user_id: number
|
user_id: number
|
||||||
}) {
|
}) {
|
||||||
return callByUser<Coupon>("/api/admin/coupon/update/assign", data)
|
return callByUser<Coupon>("/api/admin/coupon/update/assign", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserCoupon(data: {
|
||||||
|
page: number
|
||||||
|
size: number
|
||||||
|
user_id: number
|
||||||
|
}) {
|
||||||
|
return callByUser<PageRecord<Coupon>>(
|
||||||
|
"/api/admin/coupon-user/page/of-user",
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { createContext, type ReactNode, useContext, useState } from "react"
|
import { createContext, type ReactNode, Suspense, useContext, useState } from "react"
|
||||||
import { twJoin } from "tailwind-merge"
|
import { twJoin } from "tailwind-merge"
|
||||||
import { Auth } from "@/components/auth"
|
import { Auth } from "@/components/auth"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
@@ -307,7 +307,7 @@ export default function Navigation() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/*Logo 区域 */}
|
{/*Logo 区域 */}
|
||||||
<Logo collapsed={collapsed} />
|
<Suspense><Logo collapsed={collapsed} /></Suspense>
|
||||||
|
|
||||||
{/* Navigation Menu */}
|
{/* Navigation Menu */}
|
||||||
<ScrollArea className="flex-1 py-3 overflow-hidden">
|
<ScrollArea className="flex-1 py-3 overflow-hidden">
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { ComputerIcon } from "lucide-react"
|
import { ComputerIcon } from "lucide-react"
|
||||||
import { getNodeEnv } from "@/actions/env"
|
import { getNodeEnv } from "@/actions/env"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|||||||
109
src/app/(root)/client/coupon/coupon.tsx
Normal file
109
src/app/(root)/client/coupon/coupon.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
"use client"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { getPagCoupon } from "@/actions/coupon"
|
||||||
|
import { DataTable, useDataTable } from "@/components/data-table"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import type { Coupon } from "@/models/coupon"
|
||||||
|
|
||||||
|
export function IssueCouponForUser(props: {
|
||||||
|
userId: number
|
||||||
|
userPhone: string
|
||||||
|
onSuccess?: () => void
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
|
const table = useDataTable<Coupon>((page, size) =>
|
||||||
|
getPagCoupon({ page, size }),
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleIssue = async (coupon: Coupon) => {
|
||||||
|
// try {
|
||||||
|
// const result = await issueCouponToUser({
|
||||||
|
// user_id: props.userId,
|
||||||
|
// coupon_id: coupon.id,
|
||||||
|
// coupon_name: coupon.name,
|
||||||
|
// })
|
||||||
|
// if (result.success) {
|
||||||
|
// toast.success(`成功发放「${coupon.name}」给用户 ${props.userPhone}`)
|
||||||
|
// props.onSuccess?.()
|
||||||
|
// setOpen(false)
|
||||||
|
// } else {
|
||||||
|
// toast.error(result.message || "发放失败")
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// const message = error instanceof Error ? error.message : "发放失败"
|
||||||
|
// toast.error(`发放优惠券失败: ${message}`)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={newOpen => {
|
||||||
|
setOpen(newOpen)
|
||||||
|
if (newOpen) {
|
||||||
|
table.refresh()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>发放优惠券</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent
|
||||||
|
className="max-h-[85vh] overflow-y-auto"
|
||||||
|
style={{ width: "auto", minWidth: "800px", maxWidth: "90vw" }}
|
||||||
|
>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>发放优惠券给 {props.userPhone}</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<DataTable<Coupon>
|
||||||
|
{...table}
|
||||||
|
columns={[
|
||||||
|
{ header: "优惠券名称", accessorKey: "name" },
|
||||||
|
{ header: "优惠券金额", accessorKey: "amount" },
|
||||||
|
{ header: "最低消费金额", accessorKey: "min_amount" },
|
||||||
|
{
|
||||||
|
header: "状态",
|
||||||
|
accessorKey: "status",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const status = row.original.status
|
||||||
|
if (status === 0) {
|
||||||
|
return <span className="text-yellow-600">禁用</span>
|
||||||
|
}
|
||||||
|
if (status === 1) {
|
||||||
|
return <span className="text-green-600">正常</span>
|
||||||
|
}
|
||||||
|
return <span>-</span>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "action",
|
||||||
|
meta: { pin: "right" },
|
||||||
|
header: "操作",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Button size="sm" onClick={() => handleIssue(row.original)}>
|
||||||
|
发放
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="ghost">取消</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
275
src/app/(root)/client/coupon/page.tsx
Normal file
275
src/app/(root)/client/coupon/page.tsx
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
"use client"
|
||||||
|
import { format } from "date-fns"
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import { Suspense, useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { getUserCoupon } from "@/actions/coupon"
|
||||||
|
import { DataTable, useDataTable } from "@/components/data-table"
|
||||||
|
import { Page } from "@/components/page"
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
|
import type { Coupon } from "@/models/coupon"
|
||||||
|
|
||||||
|
export default function CouponPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const userId = searchParams.get("userId")
|
||||||
|
const userPhone = searchParams.get("phone")
|
||||||
|
|
||||||
|
const table = useDataTable<Coupon>((page, size) => {
|
||||||
|
return getUserCoupon({
|
||||||
|
user_id: Number(userId),
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log(table, "table")
|
||||||
|
|
||||||
|
const expireLabel = (coupon: Coupon) => {
|
||||||
|
switch (coupon.expire_type) {
|
||||||
|
case 0:
|
||||||
|
return "永久有效"
|
||||||
|
case 1:
|
||||||
|
return coupon.expire_at
|
||||||
|
? format(new Date(coupon.expire_at), "yyyy-MM-dd")
|
||||||
|
: ""
|
||||||
|
case 2:
|
||||||
|
return coupon.expire_in ? `发放后${coupon.expire_in}天` : ""
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Button onClick={() => router.back()} className="gap-2">
|
||||||
|
返回上一级
|
||||||
|
</Button>
|
||||||
|
<span className="text-gray-600">用户手机号: {userPhone}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Suspense>
|
||||||
|
<DataTable<Coupon>
|
||||||
|
{...table}
|
||||||
|
columns={[
|
||||||
|
{ header: "优惠券名称", accessorKey: "name" },
|
||||||
|
{ header: "券码", accessorKey: "code" },
|
||||||
|
{ header: "金额", accessorKey: "amount" },
|
||||||
|
{ header: "最低消费", accessorKey: "min_amount" },
|
||||||
|
{
|
||||||
|
header: "状态",
|
||||||
|
accessorKey: "coupon_status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "过期信息",
|
||||||
|
id: "expire",
|
||||||
|
cell: ({ row }) => expireLabel(row.original),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "发放时间",
|
||||||
|
accessorKey: "issued_at",
|
||||||
|
// cell: ({ row }) =>
|
||||||
|
// format(new Date(row.original.issued_at), "yyyy-MM-dd HH:mm"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "备注",
|
||||||
|
accessorKey: "remark",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<span className="text-gray-500">
|
||||||
|
{row.original.remark || "-"}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "action",
|
||||||
|
meta: { pin: "right" },
|
||||||
|
header: "操作",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<UpdateStatusDialog
|
||||||
|
coupon={row.original}
|
||||||
|
userId={Number(userId)}
|
||||||
|
onSuccess={table.refresh}
|
||||||
|
/>
|
||||||
|
<RevokeDialog
|
||||||
|
coupon={row.original}
|
||||||
|
userId={Number(userId)}
|
||||||
|
onSuccess={table.refresh}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpdateStatusDialog({
|
||||||
|
coupon,
|
||||||
|
userId,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
coupon: Coupon
|
||||||
|
userId: number
|
||||||
|
onSuccess?: () => void
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [selectedStatus, setSelectedStatus] =
|
||||||
|
useState(
|
||||||
|
// String(coupon.coupon_status),
|
||||||
|
)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
// setLoading(true)
|
||||||
|
// try {
|
||||||
|
// const status = Number(selectedStatus)
|
||||||
|
// const result = await updateUserCouponStatus({
|
||||||
|
// user_id: userId,
|
||||||
|
// coupon_id: coupon.id,
|
||||||
|
// coupon_status: status,
|
||||||
|
// })
|
||||||
|
// if (result.success) {
|
||||||
|
// toast.success("状态更新成功")
|
||||||
|
// onSuccess?.()
|
||||||
|
// setOpen(false)
|
||||||
|
// } else {
|
||||||
|
// toast.error(result.message || "更新失败")
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// const message = error instanceof Error ? error.message : "更新失败"
|
||||||
|
// toast.error(`状态更新失败: ${message}`)
|
||||||
|
// } finally {
|
||||||
|
// setLoading(false)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button size="sm" variant="secondary">
|
||||||
|
修改状态
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-sm">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>修改优惠券状态</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="py-4">
|
||||||
|
<p className="text-sm text-gray-600 mb-3">
|
||||||
|
优惠券: {coupon.name} ({coupon.code})
|
||||||
|
</p>
|
||||||
|
<Select value={selectedStatus}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="请选择状态" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="1">未使用</SelectItem>
|
||||||
|
<SelectItem value="2">已使用</SelectItem>
|
||||||
|
<SelectItem value="3">已过期</SelectItem>
|
||||||
|
<SelectItem value="0">已撤销</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button variant="ghost">取消</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button onClick={handleConfirm} disabled={loading}>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RevokeDialog({
|
||||||
|
coupon,
|
||||||
|
userId,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
coupon: Coupon
|
||||||
|
userId: number
|
||||||
|
onSuccess?: () => void
|
||||||
|
}) {
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
// setLoading(true)
|
||||||
|
// try {
|
||||||
|
// const result = await revokeUserCoupon({
|
||||||
|
// user_id: userId,
|
||||||
|
// coupon_id: coupon.id,
|
||||||
|
// })
|
||||||
|
// if (result.success) {
|
||||||
|
// toast.success("撤销成功")
|
||||||
|
// onSuccess?.()
|
||||||
|
// } else {
|
||||||
|
// toast.error(result.message || "撤销失败")
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// const message = error instanceof Error ? error.message : "撤销失败"
|
||||||
|
// toast.error(`撤销失败: ${message}`)
|
||||||
|
// } finally {
|
||||||
|
// setLoading(false)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button size="sm" variant="destructive" disabled={loading}>
|
||||||
|
撤销
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent size="sm">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>确认撤销</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
确定要撤销「{coupon.name}」({coupon.code})
|
||||||
|
吗?此操作将标记优惠券为已撤销状态。
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction variant="destructive" onClick={handleConfirm}>
|
||||||
|
撤销
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation"
|
|
||||||
|
|
||||||
export default function IssuePage() {
|
|
||||||
const router = useRouter()
|
|
||||||
const searchParams = useSearchParams()
|
|
||||||
const userId = searchParams.get("userId")
|
|
||||||
const userPhone = searchParams.get("phone")
|
|
||||||
return <div>发放优惠券</div>
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ import { Button } from "@/components/ui/button"
|
|||||||
import { ScopeCouponWriteAssign } from "@/lib/scopes"
|
import { ScopeCouponWriteAssign } from "@/lib/scopes"
|
||||||
import type { Coupon } from "@/models/coupon"
|
import type { Coupon } from "@/models/coupon"
|
||||||
import { CreateDiscount } from "./create"
|
import { CreateDiscount } from "./create"
|
||||||
import { IssueCoupon } from "./issue"
|
import { ReleaseCoupon } from "./release"
|
||||||
import { UpdateCoupon } from "./update"
|
import { UpdateCoupon } from "./update"
|
||||||
|
|
||||||
export default function CouponPage() {
|
export default function CouponPage() {
|
||||||
@@ -114,7 +114,7 @@ export default function CouponPage() {
|
|||||||
onSuccess={table.refresh}
|
onSuccess={table.refresh}
|
||||||
/>
|
/>
|
||||||
<Auth scope={ScopeCouponWriteAssign}>
|
<Auth scope={ScopeCouponWriteAssign}>
|
||||||
<IssueCoupon
|
<ReleaseCoupon
|
||||||
coupon={row.original}
|
coupon={row.original}
|
||||||
onSuccess={table.refresh}
|
onSuccess={table.refresh}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Suspense, useCallback, useState } from "react"
|
|||||||
import { Controller, useForm } from "react-hook-form"
|
import { Controller, useForm } from "react-hook-form"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { issueCoupon } from "@/actions/coupon"
|
import { getReleaseCoupon } from "@/actions/coupon"
|
||||||
import { getPageUser } from "@/actions/user"
|
import { getPageUser } from "@/actions/user"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
@@ -33,7 +33,10 @@ const filterSchema = z.object({
|
|||||||
})
|
})
|
||||||
type FormValues = z.infer<typeof filterSchema>
|
type FormValues = z.infer<typeof filterSchema>
|
||||||
|
|
||||||
export function IssueCoupon(props: { coupon: Coupon; onSuccess?: () => void }) {
|
export function ReleaseCoupon(props: {
|
||||||
|
coupon: Coupon
|
||||||
|
onSuccess?: () => void
|
||||||
|
}) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [userList, setUserList] = useState<User[]>([])
|
const [userList, setUserList] = useState<User[]>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
@@ -104,7 +107,7 @@ export function IssueCoupon(props: { coupon: Coupon; onSuccess?: () => void }) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await issueCoupon({
|
const result = await getReleaseCoupon({
|
||||||
coupon_id: coupon.id,
|
coupon_id: coupon.id,
|
||||||
user_id: targetUser.id,
|
user_id: targetUser.id,
|
||||||
})
|
})
|
||||||
@@ -18,12 +18,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
import {
|
import { Field, FieldError, FieldLabel } from "@/components/ui/field"
|
||||||
Field,
|
|
||||||
FieldError,
|
|
||||||
FieldGroup,
|
|
||||||
FieldLabel,
|
|
||||||
} from "@/components/ui/field"
|
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -452,7 +447,7 @@ export default function CustPage() {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
`/client/issue?userId=${user.id}&phone=${user.phone}`,
|
`/client/coupon?userId=${user.id}&phone=${user.phone}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user