diff --git a/adminfront/package-lock.json b/adminfront/package-lock.json index 484e6408..524f770a 100644 --- a/adminfront/package-lock.json +++ b/adminfront/package-lock.json @@ -26,7 +26,6 @@ "react-hook-form": "^7.71.1", "react-oidc-context": "^3.3.0", "react-router-dom": "^6.28.2", - "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "zod": "^3.24.1" }, @@ -5156,16 +5155,6 @@ "dev": true, "license": "ISC" }, - "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", - "license": "MIT", - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/adminfront/package.json b/adminfront/package.json index 0ff96d03..0cbcd1fc 100644 --- a/adminfront/package.json +++ b/adminfront/package.json @@ -34,7 +34,6 @@ "react-hook-form": "^7.71.1", "react-oidc-context": "^3.3.0", "react-router-dom": "^6.28.2", - "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "zod": "^3.24.1" }, diff --git a/adminfront/src/components/ui/toaster.tsx b/adminfront/src/components/ui/toaster.tsx new file mode 100644 index 00000000..864901c9 --- /dev/null +++ b/adminfront/src/components/ui/toaster.tsx @@ -0,0 +1,35 @@ +import { AlertCircle, CheckCircle2, Info } from "lucide-react"; +import { cn } from "../../lib/utils"; +import { useToastState } from "./use-toast"; + +export function Toaster() { + const toasts = useToastState(); + + if (toasts.length === 0) return null; + + return ( +
+ {toasts.map((t) => ( +
+ {t.type === "success" && ( + + )} + {t.type === "error" && } + {t.type === "info" && } +

{t.message}

+
+ ))} +
+ ); +} diff --git a/adminfront/src/components/ui/use-toast.ts b/adminfront/src/components/ui/use-toast.ts new file mode 100644 index 00000000..adbc7f38 --- /dev/null +++ b/adminfront/src/components/ui/use-toast.ts @@ -0,0 +1,60 @@ +import * as React from "react"; + +type ToastType = "success" | "error" | "info"; + +interface Toast { + id: string; + message: string; + type: ToastType; +} + +let subscribers: ((toasts: Toast[]) => void)[] = []; +let toasts: Toast[] = []; + +const notify = () => { + for (const sub of subscribers) { + sub(toasts); + } +}; + +const toastBase = (message: string, type: ToastType = "success") => { + const id = Math.random().toString(36).substring(2, 9); + toasts = [...toasts, { id, message, type }]; + notify(); + + setTimeout(() => { + toasts = toasts.filter((t) => t.id !== id); + notify(); + }, 3000); +}; + +export const toast = Object.assign(toastBase, { + success: (message: string, options?: { description?: string }) => { + const finalMessage = options?.description ? `${message} +${options.description}` : message; + toastBase(finalMessage, "success"); + }, + error: (message: string, options?: { description?: string }) => { + const finalMessage = options?.description ? `${message} +${options.description}` : message; + toastBase(finalMessage, "error"); + }, + info: (message: string, options?: { description?: string }) => { + const finalMessage = options?.description ? `${message} +${options.description}` : message; + toastBase(finalMessage, "info"); + } +}); + +export const useToastState = () => { + const [state, setState] = React.useState(toasts); + + React.useEffect(() => { + subscribers.push(setState); + return () => { + subscribers = subscribers.filter((sub) => sub !== setState); + }; + }, []); + + return state; +}; diff --git a/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx b/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx index ad7b27eb..1a993c18 100644 --- a/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx +++ b/adminfront/src/features/tenants/components/OrgChartUploadModal.tsx @@ -2,7 +2,7 @@ import { useMutation } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { Download, FileText, Loader2, Upload } from "lucide-react"; import * as React from "react"; -import { toast } from "sonner"; +import { toast } from "../../../components/ui/use-toast"; import { Button } from "../../../components/ui/button"; import { Dialog, diff --git a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx index 33faca35..5186823c 100644 --- a/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx +++ b/adminfront/src/features/tenants/routes/TenantAdminsAndOwnersTab.tsx @@ -12,7 +12,7 @@ import { import { useState } from "react"; import { useAuth } from "react-oidc-context"; import { useParams } from "react-router-dom"; -import { toast } from "sonner"; +import { toast } from "../../../components/ui/use-toast"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { diff --git a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx index 511f6680..6d56c737 100644 --- a/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantGroupsPage.tsx @@ -18,7 +18,7 @@ import { import type React from "react"; import { useState } from "react"; import { useParams } from "react-router-dom"; -import { toast } from "sonner"; +import { toast } from "../../../components/ui/use-toast"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { diff --git a/adminfront/src/features/tenants/routes/TenantProfilePage.tsx b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx index aba6f9d5..17b787c3 100644 --- a/adminfront/src/features/tenants/routes/TenantProfilePage.tsx +++ b/adminfront/src/features/tenants/routes/TenantProfilePage.tsx @@ -3,7 +3,7 @@ import type { AxiosError } from "axios"; import { Save, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import { toast } from "sonner"; +import { toast } from "../../../components/ui/use-toast"; import { Button } from "../../../components/ui/button"; import { Card, diff --git a/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx b/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx index 221c9a90..c3c9f1d5 100644 --- a/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx +++ b/adminfront/src/features/tenants/routes/TenantSchemaPage.tsx @@ -3,7 +3,7 @@ import type { AxiosError } from "axios"; import { Plus, Save, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; -import { toast } from "sonner"; +import { toast } from "../../../components/ui/use-toast"; import { Button } from "../../../components/ui/button"; import { Card, diff --git a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx index ad60e0d3..d7f73e79 100644 --- a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx +++ b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx @@ -20,7 +20,7 @@ import { import * as React from "react"; import { useMemo, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; -import { toast } from "sonner"; +import { toast } from "../../../components/ui/use-toast"; import { Badge } from "../../../components/ui/badge"; import { Button } from "../../../components/ui/button"; import { @@ -266,7 +266,7 @@ const MemberTable: React.FC<{

{t("msg.admin.users.list.empty", "멤버가 없습니다.")}