first commit
This commit is contained in:
24
app/usr/1_dashboard/page.tsx
Normal file
24
app/usr/1_dashboard/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
// app/dashboard/page.tsx (Server Component)
|
||||
import { auth } from "@/auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import DashboardClient from "@/app/components/Dashboard"; // 기존 코드 대부분 이동
|
||||
|
||||
export default async function Page() {
|
||||
const session = await auth();
|
||||
if (!session) redirect("/login");
|
||||
|
||||
// 초기 데이터 서버에서 미리 가져오면 더 빠르고 안전 (예시)
|
||||
// const [channels, contents] = await Promise.all([
|
||||
// fetch(`${process.env.API_URL}/channels`, { cache: "no-store" }).then(r=>r.json()),
|
||||
// fetch(`${process.env.API_URL}/contents`, { cache: "no-store" }).then(r=>r.json()),
|
||||
// ]);
|
||||
|
||||
return (
|
||||
<DashboardClient
|
||||
// initialChannels={channels}
|
||||
// initialContents={contents}
|
||||
user={session.user} // id/email/name 등
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
236
app/usr/2_mychannel/page.tsx
Normal file
236
app/usr/2_mychannel/page.tsx
Normal file
@@ -0,0 +1,236 @@
|
||||
"use client";
|
||||
import CalenderSelector from "@/app/components/CalenderSelector";
|
||||
import Modal from "@/app/components/Modal";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
|
||||
export default function Page() {
|
||||
const [isreqmodal, setIsreqmodal] = useState(false);
|
||||
const [chanellist, setChanellist] = useState<string[]>([]);
|
||||
const [contentlist, setContentlist] = useState<{
|
||||
id: string;
|
||||
email: string;
|
||||
pubDate: string;
|
||||
is_approved: boolean;
|
||||
is_certified: boolean;
|
||||
video_count: number;
|
||||
views: number;
|
||||
revenue: number;
|
||||
pendingRevenue: number;
|
||||
}[]>([]);
|
||||
|
||||
const [channelList, setChannelList] = useState<{
|
||||
id: string;
|
||||
handle: string;
|
||||
createtime: string;
|
||||
is_approved: boolean;
|
||||
icon: string;
|
||||
}[]>([]);
|
||||
|
||||
|
||||
const [registerCode, setRegisterCode] = useState<string>("");
|
||||
|
||||
const handleInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
|
||||
const formatNumberWithCommas = (number: number): string => {
|
||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
};
|
||||
|
||||
const get_code = async () => {
|
||||
if (handleInputRef.current && handleInputRef.current.value) {
|
||||
const inputValue = handleInputRef.current.value;
|
||||
if (confirm(`핸들 "${inputValue}" 의 등록 코드를 발급하시겠습니까?`)) {
|
||||
try {
|
||||
const response = await fetch(`/api/channel_code?handle=${inputValue}`);
|
||||
if (response.status === 409) {
|
||||
alert("이미 등록된 요청입니다. 기존 코드를 사용합니다.");
|
||||
const data = await response.json();
|
||||
setRegisterCode(data.randomcode);
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error('서버 응답이 올바르지 않습니다.');
|
||||
}
|
||||
const data = await response.json();
|
||||
setRegisterCode(data.randomcode);
|
||||
} catch (error) {
|
||||
console.error('채널 코드 요청 중 오류 발생:', error);
|
||||
alert('채널 코드를 가져오는데 실패했습니다. 다시 시도해주세요.');
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else{ alert("채널 핸들을 입력해주세요"); }
|
||||
}
|
||||
|
||||
const register_channel = async () => {
|
||||
if (handleInputRef.current && handleInputRef.current.value) {
|
||||
const inputValue = handleInputRef.current.value;
|
||||
try {
|
||||
const response = await fetch(`/api/register_channel?handle=${encodeURIComponent(inputValue)}`);
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
alert(data?.error ?? '등록 요청 실패');
|
||||
return;
|
||||
}
|
||||
alert('등록 요청을 전송했습니다.');
|
||||
setIsreqmodal(false);
|
||||
} catch (error) {
|
||||
console.error('등록 요청 중 오류:', error);
|
||||
alert('등록 요청 실패');
|
||||
}
|
||||
} else { alert("채널 핸들을 입력해주세요"); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const fetchListChannel = async () => {
|
||||
try {
|
||||
const resp = await fetch('/api/list_channel', { cache: 'no-store' });
|
||||
const data = await resp.json();
|
||||
setChannelList(data.items);
|
||||
console.log('list_channel:', data);
|
||||
} catch (e) {
|
||||
console.error('list_channel 요청 에러:', e);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// 세션 이메일 기준 채널 목록 조회 로그
|
||||
fetchListChannel();
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-[100vw] p-2 lg:w-[calc(100vw-350px)] max-w-[1200px] mx-auto">
|
||||
<div className="flex flex-row h-[64px] justify-between items-center">
|
||||
<div className="flex flex-row h-[36px]">
|
||||
|
||||
</div>
|
||||
<div className="flex flex-row h-[36px]">
|
||||
<div
|
||||
className="w-[104px] h-[36px] rounded-lg flex items-center justify-center bg-[#F94B37] border-[#D73B29] text-white cursor-pointer hover:bg-[#D73B29]"
|
||||
onClick={() => {setIsreqmodal(true); setRegisterCode("")}}
|
||||
>
|
||||
채널 등록
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="
|
||||
border-1 border-[#e6e9ef] rounded-lg bg-white overflow-y-auto
|
||||
">
|
||||
<table className="w-full h-full border-separate border-spacing-y-1 table-fixed px-[10px]">
|
||||
<colgroup>
|
||||
<col className="min-w-[250px] max-w-[300px]"/>
|
||||
<col className="w-[120px]"/>
|
||||
<col className="w-[80px]"/>
|
||||
<col className="w-[100px]"/>
|
||||
<col className="w-[100px]"/>
|
||||
<col className="w-[110px]"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr className="sticky top-0 bg-white h-[49px] ">
|
||||
<th className="border-b-1 right-border border-[#e6e9ef] ">채널 핸들</th>
|
||||
<th className="border-b-1 right-border border-[#e6e9ef] ">등록 날짜</th>
|
||||
<th className="border-b-1 right-border border-[#e6e9ef] ">승인여부</th>
|
||||
<th className="border-b-1 right-border border-[#e6e9ef] ">영상수</th>
|
||||
<th className="border-b-1 right-border border-[#e6e9ef] ">조회수</th>
|
||||
<th className="border-b-1 border-[#e6e9ef] ">예상수익</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
channelList.map((channel)=>{
|
||||
return (
|
||||
<tr key={channel.id} className="h-[54px] border-1 border-[#e6e9ef] rounded-lg font-semibold">
|
||||
<td className="right-border rounded-l-lg border-l-1 border-t-1 border-b-1 border-[#e6e9ef] pl-2 h-[54px] " >
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<div className="w-[48px] h-[48px] rounded-full border-1 border-[#e6e9ef]">
|
||||
<img src={channel.icon} alt="channel icon" className="w-[48px] h-[48px] rounded-full" />
|
||||
</div>
|
||||
<div>
|
||||
{channel.handle}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="right-border border-b-1 border-t-1 border-[#e6e9ef] text-center whitespace-nowrap overflow-hidden">{channel.createtime.split("T")[0]}</td>
|
||||
<td className="right-border border-b-1 border-t-1 border-[#e6e9ef] text-center whitespace-nowrap overflow-hidden">{channel.is_approved? "승인" : "미승인"}</td>
|
||||
<td className="right-border border-b-1 border-t-1 border-[#e6e9ef] text-center"> - </td>
|
||||
<td className="right-border border-b-1 border-t-1 border-[#e6e9ef] text-center"> - </td>
|
||||
<td className="border-b-1 border-t-1 border-[#e6e9ef] border-r-1 rounded-r-lg text-center"> - </td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Modal isOpen={isreqmodal} onClose={() => setIsreqmodal(false)}>
|
||||
<div className=" w-full h-full flex flex-col justify-start gap-5">
|
||||
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<div className="text-3xl font-semibold">채널등록</div>
|
||||
<div className="text-3xl cursor-pointer" onClick={() => setIsreqmodal(false)}>
|
||||
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M0.951569 0.451569C1.4202 -0.0170597 2.18 -0.0170597 2.64863 0.451569L9.0001 6.80304L15.3516 0.451569C15.8202 -0.0170597 16.58 -0.0170597 17.0486 0.451569C17.5173 0.920199 17.5173 1.68 17.0486 2.14863L10.6972 8.5001L17.0486 14.8516C17.5173 15.3202 17.5173 16.08 17.0486 16.5486C16.58 17.0173 15.8202 17.0173 15.3516 16.5486L9.0001 10.1972L2.64863 16.5486C2.18 17.0173 1.4202 17.0173 0.951569 16.5486C0.48294 16.08 0.48294 15.3202 0.951569 14.8516L7.30304 8.5001L0.951569 2.14863C0.48294 1.68 0.48294 0.920199 0.951569 0.451569Z" fill="#848484" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-col sh-[287px]">
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="text-lg font-semibold text-black">핸들</div>
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<div className="w-[400px] h-[56px] border-[#d5d5d5] border-1 bg-white text-black font-normal rounded-lg flex items-center justify-center text-md p-1 px-2">
|
||||
<input type="text" className="w-full h-full border-none outline-none" ref={handleInputRef}/>
|
||||
</div>
|
||||
<div className="ml-2 w-[140px] h-[50px] border-[#D73B29] bg-[#F94B37] text-white font-bold rounded-lg flex items-center justify-center text-xl cursor-pointer hover:bg-[#D73B29] transition-colors" onClick={() => get_code()}>
|
||||
코드 발급
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-col justify-between mt-5">
|
||||
<div className="text-lg font-semibold text-black">채널 등록 코드</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
|
||||
|
||||
<div className="w-full h-[56px] border-[#d5d5d5] border-1 bg-white text-black font-normal rounded-lg flex items-center justify-start text-xl p-5">
|
||||
{registerCode ? registerCode : "코드를 발급해주세요"}
|
||||
</div>
|
||||
{registerCode && (
|
||||
<div
|
||||
className="w-[45px] h-[56px] border-[#d5d5d5] border-1 bg-white rounded-lg flex items-center justify-center cursor-pointer"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(registerCode);
|
||||
}}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 9H11C9.89543 9 9 9.89543 9 11V20C9 21.1046 9.89543 22 11 22H20C21.1046 22 22 21.1046 22 20V11C22 9.89543 21.1046 9 20 9Z" stroke="#666666" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
<path d="M5 15H4C3.46957 15 2.96086 14.7893 2.58579 14.4142C2.21071 14.0391 2 13.5304 2 13V4C2 3.46957 2.21071 2.96086 2.58579 2.58579C2.96086 2.21071 3.46957 2 4 2H13C13.5304 2 14.0391 2.21071 14.4142 2.58579C14.7893 2.96086 15 3.46957 15 4V5" stroke="#666666" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div className="mt-3 w-[100%-50px] h-[56px] border-[#D73B29] bg-[#F94B37] text-white font-bold rounded-lg flex items-center justify-center text-xl cursor-pointer hover:bg-[#D73B29] transition-colors" onClick={() => register_channel()}>
|
||||
채널 등록
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
127
app/usr/3_jsmanage/page.tsx
Normal file
127
app/usr/3_jsmanage/page.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
|
||||
'use client'
|
||||
import Modal from "@/app/components/Modal";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const [noticeList, setNoticeList] = useState<{
|
||||
id: string;
|
||||
title: string;
|
||||
is_approved: boolean;
|
||||
createdAt: Date;
|
||||
}[]>([]);
|
||||
const [isreqmodal, setIsreqmodal] = useState(false);
|
||||
|
||||
const [isAdmin, setIsAdmin] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setNoticeList([
|
||||
{
|
||||
id: '1',
|
||||
title: '8월 정산 요청',
|
||||
is_approved: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '7월 정산 요청',
|
||||
is_approved: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '6월 정산 요청',
|
||||
is_approved: true,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
]);
|
||||
}, []);
|
||||
return (
|
||||
<div className="bg-white h-full">
|
||||
{isAdmin && (
|
||||
<div className="w-full flex flex-row justify-end items-center px-4 max-w-[800px] mx-auto">
|
||||
<div
|
||||
className="w-[86px] h-[36px] border-1 border-[#d5d5d5] bg-[#f94b37] text-white font-semibold rounded-lg flex items-center justify-center my-4 hover:bg-[#d73b29] cursor-pointer"
|
||||
onClick={() => setIsreqmodal(true)}
|
||||
>
|
||||
정산요청
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-[calc(100%-80px)] min-w-[500px] max-w-[800px] mx-auto overflow-y-auto p-4 flex flex-col">
|
||||
{noticeList.map((notice) => (
|
||||
<div key={notice.id} className="w-full h-[75px] border-[#d5d5d5] border-1 rounded-lg p-4 my-2 flex flex-row justify-between hover:border-[#F94B37] cursor-pointer">
|
||||
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="flex flex-row justify-start ">
|
||||
<div className="mr-2 flex items-center justify-center">
|
||||
{!notice.is_approved && ( <div className="w-[40px] h-[20px] bg-[#FEDBD7] text-[#F94B37] font-semibold text-xs rounded-md text-center flex items-center justify-center">미정산</div>
|
||||
)}
|
||||
{notice.is_approved && ( <div className="w-[50px] h-[20px] bg-[#d5d5d5] text-black font-semibold text-xs rounded-md text-center flex items-center justify-center">정산완료</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className="text-normal font-semibold flex justify-center items-center truncate overflow-hidden"> {notice.title} </div>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[#848484]">
|
||||
{notice.createdAt.toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 15L12.5 10L7.5 5" stroke="#848484" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Modal isOpen={isreqmodal} onClose={() => setIsreqmodal(false)}>
|
||||
<div className=" w-full h-full flex flex-col justify-between">
|
||||
|
||||
<div className="flex flex-row justify-between items-center">
|
||||
<div className="text-3xl font-semibold">정산요청</div>
|
||||
<div className="text-3xl cursor-pointer" onClick={() => setIsreqmodal(false)}>
|
||||
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M0.951569 0.451569C1.4202 -0.0170597 2.18 -0.0170597 2.64863 0.451569L9.0001 6.80304L15.3516 0.451569C15.8202 -0.0170597 16.58 -0.0170597 17.0486 0.451569C17.5173 0.920199 17.5173 1.68 17.0486 2.14863L10.6972 8.5001L17.0486 14.8516C17.5173 15.3202 17.5173 16.08 17.0486 16.5486C16.58 17.0173 15.8202 17.0173 15.3516 16.5486L9.0001 10.1972L2.64863 16.5486C2.18 17.0173 1.4202 17.0173 0.951569 16.5486C0.48294 16.08 0.48294 15.3202 0.951569 14.8516L7.30304 8.5001L0.951569 2.14863C0.48294 1.68 0.48294 0.920199 0.951569 0.451569Z" fill="#848484" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-between h-[287px]">
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="text-lg font-semibold text-black">총 미정산 수익</div>
|
||||
<div className="w-[428px] h-[56px] border-[#d5d5d5] border-1 bg-white text-black font-bold rounded-lg flex items-center justify-center text-xl">
|
||||
000,000,000원
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="text-lg font-semibold text-black">정산 요청 금액</div>
|
||||
<div className="w-[428px] h-[56px] border-[#d5d5d5] border-1 bg-white text-black font-bold rounded-lg flex items-center justify-center text-xl">
|
||||
000,000,000원
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="text-lg font-semibold text-black">정산 요청 채널</div>
|
||||
<div className="w-[428px] h-[56px] border-[#d5d5d5] border-1 bg-white text-black font-normal rounded-lg flex items-center justify-starttext-xl p-5">
|
||||
@ 채널명 포함 3개 채널
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="w-[428px] h-[56px] border-[#D73B29] bg-[#F94B37] text-white font-bold rounded-lg flex items-center justify-center text-xl cursor-pointer" onClick={() => setIsreqmodal(false)}>
|
||||
요청 하기
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
141
app/usr/4_noticeboard/page.tsx
Normal file
141
app/usr/4_noticeboard/page.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
'use client'
|
||||
import { useEffect, useState } from "react";
|
||||
import NoticeModal from "@/app/components/ModelNotice";
|
||||
import { MilkdownViewer } from "@/app/components/MilkdownViewer";
|
||||
import { MilkdownProvider } from "@milkdown/react";
|
||||
import { Crepe } from "@milkdown/crepe";
|
||||
import { useRef } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const crepeRef = useRef<Crepe>(null!);
|
||||
const [noticeList, setNoticeList] = useState<{
|
||||
id: string;
|
||||
title: string;
|
||||
pubDate: Date;
|
||||
content: string;
|
||||
tag: string;
|
||||
}[]>([]);
|
||||
|
||||
const [currentNotice, setCurrentNotice] = useState<{
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
tag:string
|
||||
pubDate: Date;
|
||||
}>({
|
||||
id: "",
|
||||
tag: "",
|
||||
title: "",
|
||||
content: "",
|
||||
pubDate: new Date("0"),
|
||||
});
|
||||
|
||||
|
||||
const [isNoticeModalOpen, setIsNoticeModalOpen] = useState(false);
|
||||
|
||||
const openHandler = (id: string) => {
|
||||
const notice = noticeList.find((notice) => notice.id === id);
|
||||
if (notice) {
|
||||
setCurrentNotice({
|
||||
id: notice.id,
|
||||
title: notice.title,
|
||||
content: notice.content,
|
||||
tag: notice.tag,
|
||||
pubDate: notice.pubDate,
|
||||
});
|
||||
}
|
||||
setIsNoticeModalOpen(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
const fetchNotices = async () => {
|
||||
const response = await fetch('/api/notice');
|
||||
const data = await response.json();
|
||||
setNoticeList(data);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchNotices();
|
||||
}, []);
|
||||
return (
|
||||
<div className="bg-white h-full w-[100vw] lg:w-[calc(100vw-360px)]">
|
||||
|
||||
<div className="h-[calc(100%-80px)] max-w-[800px] mx-auto overflow-y-auto p-4 flex flex-col">
|
||||
{noticeList.length === 0 ? (
|
||||
<div className="text-center text-gray-500">게시글이 없습니다.</div>
|
||||
) : (
|
||||
noticeList.map((notice) => (
|
||||
<div
|
||||
key={notice.id}
|
||||
className="h-[75px] border-[#d5d5d5] border-1 rounded-lg p-4 my-2 flex flex-row justify-between hover:border-[#F94B37] cursor-pointer min-w-[100px] shrink-0 basis-0 grow-0"
|
||||
onClick={() => openHandler(notice.id)}
|
||||
>
|
||||
|
||||
{/* 여기 min-w-0 추가해서 자식을 제한함 부모와 이 위치 관계 다시 확인 */}
|
||||
<div className="flex flex-col justify-between min-w-0">
|
||||
<div className="flex flex-row justify-start">
|
||||
|
||||
{
|
||||
notice.tag === "중요" && (
|
||||
<div className="mr-2 flex items-center justify-center">
|
||||
<div className="w-[33px] h-[20px] bg-[#FEDBD7] text-[#F94B37] font-semibold text-xs rounded-lg text-center flex items-center justify-center">중요</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className="text-normal font-semibold truncate">
|
||||
{notice.title}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[#848484]">
|
||||
{notice.pubDate.toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 15L12.5 10L7.5 5" stroke="#848484" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<NoticeModal isOpen={isNoticeModalOpen} onClose={() => setIsNoticeModalOpen(false)}>
|
||||
<div className="flex flex-col h-full bg-white rounded-lg p-6">
|
||||
<div className="flex justify-end mb-4">
|
||||
<button
|
||||
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600"
|
||||
onClick={() => setIsNoticeModalOpen(false)}
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col border-b border-gray-200 pb-4 mb-4">
|
||||
<h1 className="text-2xl font-bold text-gray-800">{currentNotice.title}</h1>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
{currentNotice.pubDate.toLocaleString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<MilkdownProvider>
|
||||
<MilkdownViewer value={currentNotice.content} editorRef={crepeRef} />
|
||||
</MilkdownProvider>
|
||||
</div>
|
||||
</div>
|
||||
</NoticeModal>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
0
app/usr/5_admin/page.tsx
Normal file
0
app/usr/5_admin/page.tsx
Normal file
13
app/usr/layout.tsx
Normal file
13
app/usr/layout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// app/layout.tsx (Server Component)
|
||||
import { auth } from "@/auth";
|
||||
import LayoutClient from "@/app/components/LayoutClient";
|
||||
|
||||
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||
const session = await auth();
|
||||
|
||||
return (
|
||||
<LayoutClient session={session}>
|
||||
{children}
|
||||
</LayoutClient>
|
||||
);
|
||||
}
|
||||
8
app/usr/page.tsx
Normal file
8
app/usr/page.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
export default function InitPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Initialization Page</h1>
|
||||
<p>Welcome to the initialization page. Please follow the instructions to set up your application.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
app/usr/test/page.tsx
Normal file
12
app/usr/test/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
export default function WhoAmI() {
|
||||
const { data } = useSession();
|
||||
return (
|
||||
<div>
|
||||
{data?.user?.name} / {data?.user?.email}
|
||||
<br />id: {(data?.user as any)?.id}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user