diff --git a/apps/common/features/package.json b/apps/common/features/package.json index 2ca07cfce..64051045c 100644 --- a/apps/common/features/package.json +++ b/apps/common/features/package.json @@ -29,9 +29,11 @@ "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "next": "15.3.0", + "next-themes": "^0.4.6", "react-chartjs-2": "^5.3.0", "react-dnd": "^16.0.1", "react-hook-form": "^7.54.2", + "sonner": "^2.0.1", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1", "zod": "^3.24.2", diff --git a/apps/common/features/src/app/providers/app-provider.tsx b/apps/common/features/src/app/providers/app-provider.tsx index 721f04bb6..97713c41e 100644 --- a/apps/common/features/src/app/providers/app-provider.tsx +++ b/apps/common/features/src/app/providers/app-provider.tsx @@ -5,10 +5,11 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; import { AuthWrapper } from "@/entities/auth"; import { MenuWrapper } from "@/entities/menu"; -import { StoreProvider, PromptProvider } from "@/shared/providers"; +import { Toaster } from "@/shared/ui/sonner"; import { AlertDialog } from "@/shared/ui/alert-dialog"; import { PromptDialog } from "@/shared/ui/prompt-dialog"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { StoreProvider, PromptProvider } from "@/shared/providers"; const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { const queryClient = new QueryClient(); @@ -22,6 +23,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { + diff --git a/apps/common/features/src/shared/ui/sonner.tsx b/apps/common/features/src/shared/ui/sonner.tsx new file mode 100644 index 000000000..f96a98d91 --- /dev/null +++ b/apps/common/features/src/shared/ui/sonner.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { Toaster as Sonner, type ToasterProps } from "sonner"; + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme(); + + return ( + + ); +}; + +export { Toaster }; diff --git a/apps/common/features/src/widgets/post/ui/post-action.tsx b/apps/common/features/src/widgets/post/ui/post-action.tsx index afc614bc1..4ca431f7a 100644 --- a/apps/common/features/src/widgets/post/ui/post-action.tsx +++ b/apps/common/features/src/widgets/post/ui/post-action.tsx @@ -1,5 +1,7 @@ +import { toast } from "sonner"; +import { useEffect } from "react"; import { useRouter, usePathname } from "next/navigation"; -import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useClipboard, useConfirm } from "@frontend-opensource/use-react-hooks"; import { Button } from "@common/components"; import { AuthorityButton } from "@/entities/menu-authority"; import { useMenuStore, useUserStore } from "@/shared/lib"; @@ -13,6 +15,7 @@ type PostActionProps = { function PostAction({ registerId, children, handleDelete }: PostActionProps) { const router = useRouter(); const pathname = usePathname(); + const { copyText, copied } = useClipboard(); const { confirm } = useConfirm(); const bbsInfo = useMenuStore((state) => state.bbsInfo); const userInfo = useUserStore((state) => state.userInfo); @@ -37,10 +40,22 @@ function PostAction({ registerId, children, handleDelete }: PostActionProps) { return true; }; + useEffect(() => { + if (copied) toast.success("URL이 클립보드에 복사되었습니다."); + }, [copied]); + return ( <> {children}
+ - + {bbsInfo?.ansUseYn === "Y" && ( - + )} - +
); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c75c08abc..370597ad0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: next: specifier: 15.3.0 version: 15.3.0(react-dom@18.3.1)(react@18.3.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@18.3.1)(react@18.3.1) react-chartjs-2: specifier: ^5.3.0 version: 5.3.0(chart.js@4.4.8)(react@18.3.1) @@ -108,6 +111,9 @@ importers: react-hook-form: specifier: ^7.54.2 version: 7.54.2(react@18.3.1) + sonner: + specifier: ^2.0.1 + version: 2.0.1(react-dom@18.3.1)(react@18.3.1) tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.17) @@ -238,6 +244,9 @@ importers: next-qrcode: specifier: ^2.5.1 version: 2.5.1(react@18.3.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@18.3.1)(react@18.3.1) react-chartjs-2: specifier: ^5.3.0 version: 5.3.0(chart.js@4.4.8)(react@18.3.1) @@ -247,6 +256,9 @@ importers: react-hook-form: specifier: ^7.54.2 version: 7.54.2(react@18.3.1) + sonner: + specifier: ^2.0.1 + version: 2.0.1(react-dom@18.3.1)(react@18.3.1) tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.17) @@ -7631,6 +7643,16 @@ packages: react: 18.3.1 dev: false + /next-themes@0.4.6(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^18.3.1 + react-dom: ^18.3.1 + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /next@15.1.7(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-GNeINPGS9c6OZKCvKypbL8GTsT5GhWPp4DM0fzkXJuXMilOO2EeFxuAY6JZbtk6XIl6Ws10ag3xRINDjSO5+wg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}