新增dialog
余额不足提示
This commit is contained in:
parent
4a24dd2cf2
commit
0df3237d21
@ -1,9 +1,14 @@
|
||||
import { ToastProvider } from "@/contexts/ToastContext";
|
||||
import { UserProvider } from "@/contexts/UserContext";
|
||||
import { DialogProvider } from "./DialogContext";
|
||||
export default function CombinedProviders({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return <ToastProvider>{children}</ToastProvider>;
|
||||
return (
|
||||
<ToastProvider>
|
||||
<DialogProvider>{children}</DialogProvider>
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
104
src/contexts/DialogContext.tsx
Normal file
104
src/contexts/DialogContext.tsx
Normal 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;
|
||||
};
|
@ -2,7 +2,9 @@
|
||||
import { useState } from "react";
|
||||
import queryString from "query-string";
|
||||
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 Params = Record<string, any>;
|
||||
@ -29,6 +31,9 @@ const useFetch = <T,>({ url, method, cacheTime = 0 }: UseFetch) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const { addToast } = useToast();
|
||||
const { showDialog } = useDialog();
|
||||
const t = useTranslations("fetch");
|
||||
const router = useRouter();
|
||||
const fetchData = async (params?: Params): Promise<any> => {
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
@ -53,6 +58,11 @@ const useFetch = <T,>({ url, method, cacheTime = 0 }: UseFetch) => {
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
addToast(err.message, "error");
|
||||
if (err.code === 402) {
|
||||
showDialog(t("InsufficientIntegration"), () => {
|
||||
router.push("/pricing");
|
||||
});
|
||||
}
|
||||
reject(err.message); // 失败时 reject 错误信息
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -143,6 +153,7 @@ const interceptorsResponse = <T,>(res: Response): Promise<T> => {
|
||||
});
|
||||
} else {
|
||||
return reject({
|
||||
code: data.code,
|
||||
message: data.message,
|
||||
url: requestUrl,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user