[common/features, apps/cs] feat: 게시물 퍼가기(url 복사) 기능 추가

This commit is contained in:
2025-07-02 13:43:12 +09:00
parent 20173a24ea
commit 1555c60341
9 changed files with 128 additions and 8 deletions
+2
View File
@@ -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",
@@ -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 }>) => {
<AlertDialog />
<PromptDialog />
<ConfirmDialog />
<Toaster position="top-center" />
</AuthWrapper>
</ConfirmProvider>
</PromptProvider>
@@ -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 (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
} as React.CSSProperties
}
{...props}
/>
);
};
export { Toaster };
@@ -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}
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button
size="lg"
color="emerald"
type="button"
onClick={() => copyText(window.location.href)}
>
퍼가기
</Button>
<Button
size="lg"
color="white"
+2
View File
@@ -35,9 +35,11 @@
"lucide-react": "^0.474.0",
"next": "15.3.0",
"next-qrcode": "^2.5.1",
"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",
@@ -7,6 +7,7 @@ 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 { 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";
@@ -33,6 +34,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
<PromptDialog />
<ConfirmDialog />
<SelectPromptDialog />
<Toaster position="top-center" />
</AuthWrapper>
</ConfirmProvider>
</PromptProvider>
+25
View File
@@ -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 (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
} as React.CSSProperties
}
{...props}
/>
);
};
export { Toaster };
+31 -6
View File
@@ -1,6 +1,10 @@
import { toast } from "sonner";
import { useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import { useClipboard } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
import { deleteReasonMessages } from "@/entities/post";
import { AuthorityButton } from "@/entities/menu-authority";
import { useMenuStore, useSelectPrompt, useUserStore } from "@/shared/lib";
type PostActionProps = {
@@ -17,6 +21,7 @@ const selectData = Object.entries(deleteReasonMessages).map(
function PostAction({ children, handleDelete }: PostActionProps) {
const router = useRouter();
const pathname = usePathname();
const { copyText, copied } = useClipboard();
const { prompt } = useSelectPrompt(selectData);
const bbsInfo = useMenuStore((state) => state.bbsInfo);
const userInfo = useUserStore((state) => state.userInfo);
@@ -39,10 +44,22 @@ function PostAction({ children, handleDelete }: PostActionProps) {
return true;
};
useEffect(() => {
if (copied) toast.success("URL이 클립보드에 복사되었습니다.");
}, [copied]);
return (
<>
{children}
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button
size="lg"
color="emerald"
type="button"
onClick={() => copyText(window.location.href)}
>
퍼가기
</Button>
<Button
size="lg"
color="white"
@@ -51,15 +68,17 @@ function PostAction({ children, handleDelete }: PostActionProps) {
>
목록
</Button>
<Button
<AuthorityButton
authority="mdfcnAuthrtYn"
size="lg"
type="button"
onClick={() => router.push(`${pathname}/modify`)}
>
수정
</Button>
</AuthorityButton>
{bbsInfo?.ansUseYn === "Y" && (
<Button
<AuthorityButton
authority="regAuthrtYn"
size="lg"
type="button"
color="gray"
@@ -67,11 +86,17 @@ function PostAction({ children, handleDelete }: PostActionProps) {
disabled={!checkAnsUseAuthrt()}
>
답글
</Button>
</AuthorityButton>
)}
<Button size="lg" color="red" type="button" onClick={onDelete}>
<AuthorityButton
authority="delAuthrtYn"
size="lg"
color="red"
type="button"
onClick={onDelete}
>
삭제
</Button>
</AuthorityButton>
</div>
</>
);
+22
View File
@@ -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}