mirror of
https://github.com/beilunyang/moemail.git
synced 2026-05-07 04:32:44 +08:00
refactor: Consolidate configuration management with Zustand store
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
import { getRequestContext } from "@cloudflare/next-on-pages"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function GET() {
|
||||
const env = getRequestContext().env
|
||||
const adminContact = await env.SITE_CONFIG.get("ADMIN_CONTACT")
|
||||
|
||||
return Response.json({
|
||||
adminContact: adminContact || ""
|
||||
})
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export async function GET() {
|
||||
|
||||
return Response.json({
|
||||
defaultRole: defaultRole || ROLES.CIVILIAN,
|
||||
emailDomains: emailDomains || "",
|
||||
emailDomains: emailDomains || "moemail.app",
|
||||
adminContact: adminContact || "",
|
||||
maxEmails: maxEmails || EMAIL_CONFIG.MAX_ACTIVE_EMAILS.toString()
|
||||
})
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { getRequestContext } from "@cloudflare/next-on-pages"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
export const runtime = "edge"
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const domainString = await getRequestContext().env.SITE_CONFIG.get("EMAIL_DOMAINS")
|
||||
|
||||
return NextResponse.json({ domains: domainString ? domainString.split(',') : ["moemail.app"] })
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch domains:', error)
|
||||
return NextResponse.json(
|
||||
{ error: "获取域名列表失败" },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,17 @@ import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { EXPIRY_OPTIONS } from "@/types/email"
|
||||
import { useCopy } from "@/hooks/use-copy"
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
|
||||
interface CreateDialogProps {
|
||||
onEmailCreated: () => void
|
||||
}
|
||||
|
||||
export function CreateDialog({ onEmailCreated }: CreateDialogProps) {
|
||||
const { config } = useConfig()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [emailName, setEmailName] = useState("")
|
||||
const [domains, setDomains] = useState<string[]>([])
|
||||
const [currentDomain, setCurrentDomain] = useState("")
|
||||
const [expiryTime, setExpiryTime] = useState(EXPIRY_OPTIONS[1].value.toString())
|
||||
const { toast } = useToast()
|
||||
@@ -83,16 +84,11 @@ export function CreateDialog({ onEmailCreated }: CreateDialogProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchDomains = async () => {
|
||||
const response = await fetch("/api/emails/domains");
|
||||
const data = (await response.json()) as { domains: string[] };
|
||||
setDomains(data.domains || []);
|
||||
setCurrentDomain(data.domains[0] || "");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchDomains()
|
||||
}, [])
|
||||
if ((config?.emailDomainsArray?.length ?? 0) > 0) {
|
||||
setCurrentDomain(config?.emailDomainsArray[0] ?? "")
|
||||
}
|
||||
}, [config])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
@@ -114,13 +110,13 @@ export function CreateDialog({ onEmailCreated }: CreateDialogProps) {
|
||||
placeholder="输入邮箱名"
|
||||
className="flex-1"
|
||||
/>
|
||||
{domains.length > 1 && (
|
||||
{(config?.emailDomainsArray?.length ?? 0) > 1 && (
|
||||
<Select value={currentDomain} onValueChange={setCurrentDomain}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{domains.map(d => (
|
||||
{config?.emailDomainsArray?.map(d => (
|
||||
<SelectItem key={d} value={d}>@{d}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { ROLES } from "@/lib/permissions"
|
||||
import { useUserRole } from "@/hooks/use-user-role"
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
|
||||
interface Email {
|
||||
id: string
|
||||
@@ -42,6 +43,7 @@ interface EmailResponse {
|
||||
|
||||
export function EmailList({ onEmailSelect, selectedEmailId }: EmailListProps) {
|
||||
const { data: session } = useSession()
|
||||
const { config } = useConfig()
|
||||
const { role } = useUserRole()
|
||||
const [emails, setEmails] = useState<Email[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -49,22 +51,9 @@ export function EmailList({ onEmailSelect, selectedEmailId }: EmailListProps) {
|
||||
const [nextCursor, setNextCursor] = useState<string | null>(null)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [maxEmails, setMaxEmails] = useState<number>(EMAIL_CONFIG.MAX_ACTIVE_EMAILS)
|
||||
const [emailToDelete, setEmailToDelete] = useState<Email | null>(null)
|
||||
const { toast } = useToast()
|
||||
|
||||
const fetchMaxEmails = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/config")
|
||||
if (res.ok) {
|
||||
const data = await res.json() as { maxEmails: string }
|
||||
setMaxEmails(Number(data.maxEmails))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch max emails:", error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchEmails = async (cursor?: string) => {
|
||||
try {
|
||||
const url = new URL("/api/emails", window.location.origin)
|
||||
@@ -125,10 +114,6 @@ export function EmailList({ onEmailSelect, selectedEmailId }: EmailListProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (session) fetchEmails()
|
||||
if (session && role !== ROLES.EMPEROR) {
|
||||
fetchMaxEmails()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [session])
|
||||
|
||||
const handleDelete = async (email: Email) => {
|
||||
@@ -189,7 +174,7 @@ export function EmailList({ onEmailSelect, selectedEmailId }: EmailListProps) {
|
||||
{role === ROLES.EMPEROR ? (
|
||||
`${total}/∞ 个邮箱`
|
||||
) : (
|
||||
`${total}/${maxEmails} 个邮箱`
|
||||
`${total}/${config?.maxEmails || EMAIL_CONFIG.MAX_ACTIVE_EMAILS} 个邮箱`
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useAdminContact } from "@/hooks/use-admin-contact"
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
export function NoPermissionDialog() {
|
||||
const router = useRouter()
|
||||
const { adminContact } = useAdminContact()
|
||||
const { config } = useConfig()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-background/50 backdrop-blur-sm z-50">
|
||||
@@ -15,8 +15,8 @@ export function NoPermissionDialog() {
|
||||
<h1 className="text-xl md:text-2xl font-bold">权限不足</h1>
|
||||
<p className="text-sm md:text-base text-muted-foreground">你没有权限访问此页面,请联系网站管理员</p>
|
||||
{
|
||||
adminContact && (
|
||||
<p className="text-sm md:text-base text-muted-foreground">管理员联系方式:{adminContact}</p>
|
||||
config?.adminContact && (
|
||||
<p className="text-sm md:text-base text-muted-foreground">管理员联系方式:{config.adminContact}</p>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Label } from "@/components/ui/label"
|
||||
import { useCopy } from "@/hooks/use-copy"
|
||||
import { useRolePermission } from "@/hooks/use-role-permission"
|
||||
import { PERMISSIONS } from "@/lib/permissions"
|
||||
import { useAdminContact } from "@/hooks/use-admin-contact"
|
||||
import { useConfig } from "@/hooks/use-config"
|
||||
|
||||
type ApiKey = {
|
||||
id: string
|
||||
@@ -68,7 +68,7 @@ export function ApiKeyPanel() {
|
||||
}
|
||||
}, [canManageApiKey])
|
||||
|
||||
const { adminContact } = useAdminContact()
|
||||
const { config } = useConfig()
|
||||
|
||||
const createApiKey = async () => {
|
||||
if (!newKeyName.trim()) return
|
||||
@@ -248,8 +248,8 @@ export function ApiKeyPanel() {
|
||||
<p>需要公爵或更高权限才能管理 API Key</p>
|
||||
<p className="mt-2">请联系网站管理员升级您的角色</p>
|
||||
{
|
||||
adminContact && (
|
||||
<p className="mt-2">管理员联系方式:{adminContact}</p>
|
||||
config?.adminContact && (
|
||||
<p className="mt-2">管理员联系方式:{config.adminContact}</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useToast } from "@/components/ui/use-toast"
|
||||
|
||||
export function useAdminContact() {
|
||||
const [adminContact, setAdminContact] = useState("")
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { toast } = useToast()
|
||||
|
||||
const fetchAdminContact = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/admin-contact")
|
||||
if (!res.ok) throw new Error("获取管理员联系方式失败")
|
||||
const data = await res.json() as { adminContact: string }
|
||||
setAdminContact(data.adminContact)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast({
|
||||
title: "获取失败",
|
||||
description: "获取管理员联系方式失败",
|
||||
variant: "destructive"
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchAdminContact()
|
||||
}, [])
|
||||
|
||||
return {
|
||||
adminContact,
|
||||
loading,
|
||||
refreshAdminContact: fetchAdminContact
|
||||
}
|
||||
}
|
||||
62
app/hooks/use-config.ts
Normal file
62
app/hooks/use-config.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
"use client"
|
||||
|
||||
import { create } from "zustand"
|
||||
import { Role, ROLES } from "@/lib/permissions"
|
||||
import { EMAIL_CONFIG } from "@/config"
|
||||
import { useEffect } from "react"
|
||||
|
||||
interface Config {
|
||||
defaultRole: Exclude<Role, typeof ROLES.EMPEROR>
|
||||
emailDomains: string
|
||||
emailDomainsArray: string[]
|
||||
adminContact: string
|
||||
maxEmails: number
|
||||
}
|
||||
|
||||
interface ConfigStore {
|
||||
config: Config | null
|
||||
loading: boolean
|
||||
error: string | null
|
||||
fetch: () => Promise<void>
|
||||
}
|
||||
|
||||
const useConfigStore = create<ConfigStore>((set) => ({
|
||||
config: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
fetch: async () => {
|
||||
try {
|
||||
set({ loading: true, error: null })
|
||||
const res = await fetch("/api/config")
|
||||
if (!res.ok) throw new Error("获取配置失败")
|
||||
const data = await res.json() as Config
|
||||
set({
|
||||
config: {
|
||||
defaultRole: data.defaultRole || ROLES.CIVILIAN,
|
||||
emailDomains: data.emailDomains,
|
||||
emailDomainsArray: data.emailDomains.split(','),
|
||||
adminContact: data.adminContact || "",
|
||||
maxEmails: Number(data.maxEmails) || EMAIL_CONFIG.MAX_ACTIVE_EMAILS
|
||||
},
|
||||
loading: false
|
||||
})
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : "获取配置失败",
|
||||
loading: false
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
export function useConfig() {
|
||||
const store = useConfigStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (!store.config && !store.loading) {
|
||||
store.fetch()
|
||||
}
|
||||
}, [store.config, store.loading])
|
||||
|
||||
return store
|
||||
}
|
||||
@@ -60,6 +60,5 @@ export const config = {
|
||||
'/api/roles/:path*',
|
||||
'/api/config/:path*',
|
||||
'/api/api-keys/:path*',
|
||||
'/api/admin-contact',
|
||||
]
|
||||
}
|
||||
@@ -48,7 +48,8 @@
|
||||
"react-dom": "19.0.0",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.24.1"
|
||||
"zod": "^3.24.1",
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241127.0",
|
||||
|
||||
46
pnpm-lock.yaml
generated
46
pnpm-lock.yaml
generated
@@ -95,6 +95,9 @@ importers:
|
||||
zod:
|
||||
specifier: ^3.24.1
|
||||
version: 3.24.1
|
||||
zustand:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3(@types/react@18.3.14)(react@19.0.0)
|
||||
devDependencies:
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20241127.0
|
||||
@@ -1267,79 +1270,67 @@ packages:
|
||||
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.0.5':
|
||||
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.0.4':
|
||||
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.0.4':
|
||||
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.33.5':
|
||||
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.33.5':
|
||||
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.33.5':
|
||||
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.33.5':
|
||||
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
|
||||
@@ -1428,56 +1419,48 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.1.1':
|
||||
resolution: {integrity: sha512-I5Q6M3T9jzTUM2JlwTBy/VBSX+YCDvPLnSaJX5wE5GEPeaJkipMkvTA9+IiFK5PG5ljXTqVFVUj5BSHiYLCpoQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@13.5.7':
|
||||
resolution: {integrity: sha512-A06vkj+8X+tLRzSja5REm/nqVOCzR+x5Wkw325Q/BQRyRXWGCoNbQ6A+BR5M86TodigrRfI3lUZEKZKe3QJ9Bg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.1.1':
|
||||
resolution: {integrity: sha512-4cPMSYmyXlOAk8U04ouEACEGnOwYM9uJOXZnm9GBXIKRbNEvBOH9OePhHiDWqOws6iaHvGayaKr+76LmM41yJA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@13.5.7':
|
||||
resolution: {integrity: sha512-UdHm7AlxIbdRdMsK32cH0EOX4OmzAZ4Xm+UVlS0YdvwLkI3pb7AoBEoVMG5H0Wj6Wpz6GNkrFguHTRLymTy6kw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.1.1':
|
||||
resolution: {integrity: sha512-KgIiKDdV35KwL9TrTxPFGsPb3J5RuDpw828z3MwMQbWaOmpp/T4MeWQCwo+J2aOxsyAcfsNE334kaWXCb6YTTA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@13.5.7':
|
||||
resolution: {integrity: sha512-c50Y8xBKU16ZGj038H6C13iedRglxvdQHD/1BOtes56gwUrIRDX2Nkzn3mYtpz3Wzax0gfAF9C0Nqljt93IxvA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.1.1':
|
||||
resolution: {integrity: sha512-aHP/29x8loFhB3WuW2YaWaYFJN389t6/SBsug19aNwH+PRLzDEQfCvtuP6NxRCido9OAoExd+ZuYJKF9my1Kpg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@13.5.7':
|
||||
resolution: {integrity: sha512-NcUx8cmkA+JEp34WNYcKW6kW2c0JBhzJXIbw+9vKkt9m/zVJ+KfizlqmoKf04uZBtzFN6aqE2Fyv2MOd021WIA==}
|
||||
@@ -5613,6 +5596,24 @@ packages:
|
||||
zod@3.24.1:
|
||||
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
|
||||
|
||||
zustand@5.0.3:
|
||||
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=18.0.0'
|
||||
immer: '>=9.0.6'
|
||||
react: '>=18.0.0'
|
||||
use-sync-external-store: '>=1.2.0'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
use-sync-external-store:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
@@ -11302,3 +11303,8 @@ snapshots:
|
||||
stacktracey: 2.1.8
|
||||
|
||||
zod@3.24.1: {}
|
||||
|
||||
zustand@5.0.3(@types/react@18.3.14)(react@19.0.0):
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.14
|
||||
react: 19.0.0
|
||||
|
||||
Reference in New Issue
Block a user