Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 27 additions & 29 deletions client/src/components/admin/layout/AdminHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,38 +25,36 @@ export const AdminHeader = ({
setLogin();
};
return (
<>
<header className="border-b">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-8">
<div className="flex-shrink-0">
<img className="h-10 w-auto cursor-pointer" src={logo} alt="Logo" onClick={() => location.reload()} />
</div>
<header className="border-b">
<div className="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-8">
<button className="flex-shrink-0" onClick={() => location.reload()}>
<img className="h-10 w-auto cursor-pointer" src={logo} alt="Logo" />
</button>

<AdminNavigationMenu handleTap={handleTap} />
</div>
<AdminNavigationMenu handleTap={handleTap} />
</div>

{/* Right Side Menu */}
<div className="flex items-center space-x-4">
{/* Notifications */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<User className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem className="text-red-600" onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
로그아웃
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* Right Side Menu */}
<div className="flex items-center space-x-4">
{/* Notifications */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<User className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem className="text-red-600" onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
로그아웃
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</header>
</>
</div>
</header>
);
};
4 changes: 3 additions & 1 deletion client/src/components/admin/layout/AdminMember.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export default function AdminMember() {
};

const onError = (error: AxiosError) => {
alert(`관리자 등록 실패: ${error.response?.data || error.message}`);
const errorMessage =
typeof error.response?.data === "string" ? error.response.data : error.response?.data || error.message;
alert(`관리자 등록 실패: ${errorMessage}`);
};

const handleChange = (e: React.ChangeEvent<HTMLInputElement>, name: string) => {
Expand Down
39 changes: 22 additions & 17 deletions client/src/components/admin/layout/AdminNavigationMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { Button } from "@/components/ui/button";
import { NavigationMenu, NavigationMenuItem, NavigationMenuList } from "@/components/ui/navigation-menu";

export const AdminNavigationMenu = ({ handleTap }: { handleTap: (tap: "RSS" | "MEMBER") => void }) => {
export const TAB_TYPES = {
RSS: "RSS",
MEMBER: "MEMBER",
} as const;

type TabType = (typeof TAB_TYPES)[keyof typeof TAB_TYPES];

export const AdminNavigationMenu = ({ handleTap }: { handleTap: (tabType: TabType) => void }) => {
return (
<>
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<Button variant="ghost" className="w-full justify-start" onClick={() => handleTap("RSS")}>
RSS 목록
</Button>
</NavigationMenuItem>
<NavigationMenuItem>
<Button variant="ghost" className="w-full justify-start" onClick={() => handleTap("MEMBER")}>
회원 관리
</Button>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</>
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<Button variant="ghost" className="w-full justify-start" onClick={() => handleTap("RSS")}>
RSS 목록
</Button>
</NavigationMenuItem>
<NavigationMenuItem>
<Button variant="ghost" className="w-full justify-start" onClick={() => handleTap("MEMBER")}>
회원 관리
</Button>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
);
};
15 changes: 9 additions & 6 deletions client/src/components/admin/layout/AdminTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { AxiosError } from "axios";
import { TriangleAlert } from "lucide-react";

import { RejectModal } from "@/components/admin/rss/RejectModal";
import AcceptedTab from "@/components/admin/taps/AcceptedTap";
import PendingTab from "@/components/admin/taps/PendingTap";
import RejectedTab from "@/components/admin/taps/RejectedTap";
import AcceptedTab from "@/components/admin/tabs/AcceptedTab";
import PendingTab from "@/components/admin/tabs/PendingTab";
import RejectedTab from "@/components/admin/tabs/RejectedTab";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
Expand All @@ -16,8 +16,7 @@ import { useFetchRss, useFetchAccept, useFetchReject } from "@/hooks/queries/use
import { useAdminAccept, useAdminReject } from "@/hooks/queries/useRssActions";

import { useAdminSearchStore } from "@/store/useSearchStore";
import { AdminRssData } from "@/types/rss";
import { AdminRequest } from "@/types/rss";
import { AdminRssData, AdminRequest } from "@/types/rss";

type SelectedBlogType = {
blogName: string;
Expand Down Expand Up @@ -74,7 +73,11 @@ export const AdminTabs = ({ setLogout }: { setLogout: () => void }) => {
const { mutate: rejectMutate } = useAdminReject(onSuccess, onError);

const handleActions = (data: AdminRequest, actions: "accept" | "reject") => {
actions === "accept" ? acceptMutate(data) : rejectMutate(data);
if (actions === "accept") {
acceptMutate(data);
} else {
rejectMutate(data);
}
};

const handleSelectedBlog = ({ blogName, blogId }: SelectedBlogType) => {
Expand Down
3 changes: 1 addition & 2 deletions client/src/components/admin/login/AdminLoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ export default function AdminLogin({ setLogin }: { setLogin: () => void }) {
setLogin();
};

const onError = (error: any) => {
const onError = () => {
setLoginError(true);
console.log(error);
};
const { mutate } = useAdminAuth(onSuccess, onError);

Expand Down
46 changes: 22 additions & 24 deletions client/src/components/admin/rss/RejectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,27 @@ export const RejectModal = ({ blogName, rejectMessage, handleReason, onSubmit, o
};

return (
<>
<Dialog open={!!blogName} onOpenChange={() => onCancel()}>
<DialogContent>
<DialogHeader>
<DialogTitle>거부 사유 입력</DialogTitle>
</DialogHeader>
{blogName && <p className="text-sm text-muted-foreground">블로그: {blogName}</p>}
<Textarea
placeholder="거부 사유를 입력하세요..."
className="min-h-[120px]"
value={rejectMessage}
onChange={(e) => handleReason(e.target.value)}
/>
<DialogFooter>
<Button variant="outline" onClick={onCancel}>
취소
</Button>
<Button variant="destructive" onClick={handleSubmit} disabled={!rejectMessage.trim()}>
거부하기
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
<Dialog open={!!blogName} onOpenChange={() => onCancel()}>
<DialogContent>
<DialogHeader>
<DialogTitle>거부 사유 입력</DialogTitle>
</DialogHeader>
{blogName && <p className="text-sm text-muted-foreground">블로그: {blogName}</p>}
<Textarea
placeholder="거부 사유를 입력하세요..."
className="min-h-[120px]"
value={rejectMessage}
onChange={(e) => handleReason(e.target.value)}
/>
<DialogFooter>
<Button variant="outline" onClick={onCancel}>
취소
</Button>
<Button variant="destructive" onClick={handleSubmit} disabled={!rejectMessage.trim()}>
거부하기
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
58 changes: 28 additions & 30 deletions client/src/components/admin/rss/RssRequestCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,34 @@ interface RssRequestCardProps {

export const RssRequestCard = ({ request, onApprove, onReject }: RssRequestCardProps) => {
return (
<>
<Card key={request.id}>
<CardContent className="flex justify-between p-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">{request.name}</h3>
<p className="text-sm text-muted-foreground">{request.rssUrl}</p>
<div className="flex items-center space-x-4">
<span className="text-sm text-muted-foreground">신청자: {request.userName}</span>
</div>
<Card>
<CardContent className="flex justify-between p-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">{request.name}</h3>
<p className="text-sm text-muted-foreground">{request.rssUrl}</p>
<div className="flex items-center space-x-4">
<span className="text-sm text-muted-foreground">신청자: {request.userName}</span>
</div>
<div className="flex space-x-2">
<Button
variant="outline"
className="text-green-600 hover:text-green-700 hover:bg-green-50"
onClick={() => onApprove(request)}
>
<CheckCircle className="mr-2 h-4 w-4" />
승인
</Button>
<Button
variant="outline"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => onReject(request)}
>
<XCircle className="mr-2 h-4 w-4" />
거부
</Button>
</div>
</CardContent>
</Card>
</>
</div>
<div className="flex space-x-2">
<Button
variant="outline"
className="text-green-600 hover:text-green-700 hover:bg-green-50"
onClick={() => onApprove(request)}
>
<CheckCircle className="mr-2 h-4 w-4" />
승인
</Button>
<Button
variant="outline"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => onReject(request)}
>
<XCircle className="mr-2 h-4 w-4" />
거부
</Button>
</div>
</CardContent>
</Card>
);
};
24 changes: 11 additions & 13 deletions client/src/components/admin/rss/RssResponseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@ interface RssResponseCardProps {

export const RssResponseCard = ({ request }: RssResponseCardProps) => {
return (
<>
<Card key={request.id}>
<CardContent className="flex justify-between p-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">{request.name}</h3>
<p className="text-sm text-muted-foreground">{request.rssUrl}</p>
{request.description && <p className="text-sm text-muted-foreground">거부 사유:{request.description}</p>}
<div className="flex items-center space-x-4">
<span className="text-sm text-muted-foreground">신청자: {request.userName}</span>
</div>
<Card>
<CardContent className="flex justify-between p-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold">{request.name}</h3>
<p className="text-sm text-muted-foreground">{request.rssUrl}</p>
{request.description && <p className="text-sm text-muted-foreground">거부 사유:{request.description}</p>}
<div className="flex items-center space-x-4">
<span className="text-sm text-muted-foreground">신청자: {request.userName}</span>
</div>
</CardContent>
</Card>
</>
</div>
</CardContent>
</Card>
);
};
22 changes: 10 additions & 12 deletions client/src/components/admin/rss/RssSearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,16 @@ import { useAdminSearchStore } from "@/store/useSearchStore";
export const RssRequestSearchBar = () => {
const { searchParam, setSearchParam } = useAdminSearchStore();
return (
<>
<div className="mb-8 flex space-x-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="블로그명, URL 또는 신청자로 검색"
className="pl-10"
value={searchParam}
onChange={(e) => setSearchParam(e.target.value)}
/>
</div>
<div className="mb-8 flex space-x-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
placeholder="블로그명, URL 또는 신청자로 검색"
className="pl-10"
value={searchParam}
onChange={(e) => setSearchParam(e.target.value)}
/>
</div>
</>
</div>
);
};
Loading