新增dialog

余额不足提示
This commit is contained in:
liangzai 2024-09-23 21:09:54 +08:00
parent 4a24dd2cf2
commit 0df3237d21
3 changed files with 122 additions and 2 deletions

View File

@ -1,9 +1,14 @@
import { ToastProvider } from "@/contexts/ToastContext"; import { ToastProvider } from "@/contexts/ToastContext";
import { UserProvider } from "@/contexts/UserContext"; import { UserProvider } from "@/contexts/UserContext";
import { DialogProvider } from "./DialogContext";
export default function CombinedProviders({ export default function CombinedProviders({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return <ToastProvider>{children}</ToastProvider>; return (
<ToastProvider>
<DialogProvider>{children}</DialogProvider>
</ToastProvider>
);
} }

View File

@ -0,0 +1,104 @@
// components/DialogProvider.tsx
"use client";
import React, { createContext, useContext, useState, ReactNode } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useTranslations } from "next-intl";
interface DialogContextType {
showDialog: (
message: string,
onConfirm: () => void,
onCancel?: () => void
) => void;
}
interface Dialog {
id: number;
message: string;
onConfirm: () => void;
onCancel?: () => void;
}
const DialogContext = createContext<DialogContextType | undefined>(undefined);
export const DialogProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [dialogs, setDialogs] = useState<Dialog[]>([]);
const [nextId, setNextId] = useState(0);
const t = useTranslations("dialog");
const showDialog = (
message: string,
onConfirm: () => void,
onCancel?: () => void
) => {
setDialogs((prevDialogs) => [
...prevDialogs,
{ id: nextId, message, onConfirm, onCancel },
]);
setNextId(nextId + 1);
};
const removeDialog = (id: number) => {
setDialogs((prevDialogs) =>
prevDialogs.filter((dialog) => dialog.id !== id)
);
};
return (
<DialogContext.Provider value={{ showDialog }}>
{children}
{/* Dialog container */}
<AnimatePresence>
{dialogs.map((dialog) => (
<motion.div
key={dialog.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="fixed inset-0 flex items-center justify-center z-[9999] bg-black bg-opacity-50"
>
<motion.div
initial={{ scale: 0.5, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.5, opacity: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className="bg-white rounded-lg shadow-lg p-6 w-[300px] text-center"
>
<div className="mb-4 text-lg">{dialog.message}</div>
<div className="flex justify-end gap-3 mt-4">
<button
className="btn btn-sm"
onClick={() => {
dialog.onCancel?.();
removeDialog(dialog.id);
}}
>
{t("Cancel")}
</button>
<button
className="btn btn-secondary btn-sm"
onClick={() => {
dialog.onConfirm();
removeDialog(dialog.id);
}}
>
{t("Confirmation")}
</button>
</div>
</motion.div>
</motion.div>
))}
</AnimatePresence>
</DialogContext.Provider>
);
};
// 自定义 hook 来使用 DialogContext
export const useDialog = () => {
const context = useContext(DialogContext);
if (context === undefined) {
throw new Error("useDialog must be used within a DialogProvider");
}
return context;
};

View File

@ -2,7 +2,9 @@
import { useState } from "react"; import { useState } from "react";
import queryString from "query-string"; import queryString from "query-string";
import { useToast } from "@/contexts/ToastContext"; import { useToast } from "@/contexts/ToastContext";
import { useDialog } from "@/contexts/DialogContext";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type Params = Record<string, any>; type Params = Record<string, any>;
@ -29,6 +31,9 @@ const useFetch = <T,>({ url, method, cacheTime = 0 }: UseFetch) => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { addToast } = useToast(); const { addToast } = useToast();
const { showDialog } = useDialog();
const t = useTranslations("fetch");
const router = useRouter();
const fetchData = async (params?: Params): Promise<any> => { const fetchData = async (params?: Params): Promise<any> => {
setError(null); setError(null);
setLoading(true); setLoading(true);
@ -53,6 +58,11 @@ const useFetch = <T,>({ url, method, cacheTime = 0 }: UseFetch) => {
} catch (err: any) { } catch (err: any) {
setError(err.message); setError(err.message);
addToast(err.message, "error"); addToast(err.message, "error");
if (err.code === 402) {
showDialog(t("InsufficientIntegration"), () => {
router.push("/pricing");
});
}
reject(err.message); // 失败时 reject 错误信息 reject(err.message); // 失败时 reject 错误信息
} finally { } finally {
setLoading(false); setLoading(false);
@ -143,6 +153,7 @@ const interceptorsResponse = <T,>(res: Response): Promise<T> => {
}); });
} else { } else {
return reject({ return reject({
code: data.code,
message: data.message, message: data.message,
url: requestUrl, url: requestUrl,
}); });