일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 에러
- 백준
- react native 개발
- React
- error
- s3 upload
- AWS Access Key
- 리액트 네이티브 에러
- Next.js
- react native picker
- react native 세팅
- react native CLI
- GIT
- 문자열 대소문자
- Project
- aws bucket 정책
- img upload
- js
- Access Key 생성
- react native font
- 리액트
- AWS
- firebase 라이브러리
- 문자열 대소문자 구별
- fire base
- react native
- PongWorld
- 리액트 네이티브
- babel.config.js
- 리엑트 네이티브 아이콘
- Today
- Total
밝을희 클태
[ Next.JS ] Admin 페이지를 만들어보자 본문
Next.JS 14 App Router
회원 관리나 상품 관리를 편하게 하기 위해 어드민 페이지를 만들려고 한다.
어드민 페이지의 기능은 아래와 같이 4가지 기능이 있다.
- 전체 유저 조회
- 정지된 유저 조회
- 전체 상품 조회
- 신고가 들어온 상품 조회
로직 :
- URL의 queryString을 읽어서 page, 검색어를 받아옴
- queryString의 값으로 DB를 조회
- page나 검색어가 변경이 되면 URL에 반영
hook으로 안 하고 queryString으로 하는 이유는 hook으로 구현을 하면 새로고침시 현재 보던 페이지, 검색 키워드, 카테고리 등이 다 초기화가 된다. 그리고 내가 지금 보는 화면을 다른 사람과 공유를 할 수 없어서 queryString을 읽어서 구현을 했다.
어드민 페이지를 만들다가 기존의 레이아웃들 때문에 작업이 힘들어서 폴더 구조를 아예 바꿔주어 별도의 AdminLayout을 만들어 줬다.
route-group을 사용해서 admin과 main 폴더를 () 괄호로 묶어 그룹을 지어주어서 특정 경로일 때 해당 레이아웃을 사용하게 구조를 변경해 줬다.
- js 말고 jsx 또는 tsx 확장자를 사용할 수 있다.
- <html>, <body> 태그는 RootLayout에서만 사용할 수 있다.
📦app
┣ 📂(admin)
┃ ┗ 📂admin
┃ ┃ ┣ 📜layout.js
┃ ┃ ┗ 📜page.jsx
┣ 📂(main)
┃ ┣ 📜layout.js
┃ ┗ 📜page.jsx
┣ 📂api
┣ 📜globals.css
┗ 📜layout.js
RootLayout.js
import { Inter } from 'next/font/google';
import './globals.css';
import Nav from './(main)/_components/Nav';
import BottomNav from './(main)/_components/BottomNav/BottomNav';
import Footer from './(main)/_components/Footer';
import AuthProvider from '@/lib/next-auth';
import RQProvider from './(main)/_components/RQProvider';
import Script from 'next/script';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'KEYNUT | 커스텀 키보드',
description: 'Generated by create next app',
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta name="format-detection" content="telephone=no" />
</head>
<body className={inter.className + 'flex flex-col justify-center items-center max-md:mb-bottom-nav-heigth'}>
<Script
src="https://t1.kakaocdn.net/kakao_js_sdk/2.7.2/kakao.min.js"
integrity="sha384-TiCUE00h649CAMonG018J2ujOgDKW/kVWlChEuu4jK2vxfAAD0eZxzCKakxg55G4"
crossorigin="anonymous"
></Script>
<RQProvider>
<AuthProvider>{children}</AuthProvider>
</RQProvider>
</body>
</html>
);
}
MainLayout.js
import Footer from './_components/Footer';
import BottomNav from './_components/BottomNav/BottomNav';
import Nav from './_components/Nav';
export default function MainLayout({ children }) {
return (
<>
<Nav />
<main className="relative main-1280 max-md:pt-0">{children}</main>
<Footer />
<BottomNav />
</>
);
}
AdminLayout.js
export default function AdminLayout({ children }) {
return <>{children}</>;
}
유저 관리
유저를 관리할 수 있는 기능을 가장 먼저 만들었는데, 특히 회원 탈퇴 기능이 생각보다 복잡해서 애를 먹었다. 탈퇴할 때 사용자의 상품, 해당 상품을 북마크 한 유저, 유저의 북마크에 있는 상품, 조회 기록 등등 지워야 할 데이터가 엄청 많았다. 이 지우는 부분에는 분명히 문제가 없었는데도, 탈퇴를 시키면 일부 기록들이 지워지지 않는 문제가 발생했다.
문제는 Promise 부분에 있었다. 기록들을 지울 때 비동기로 처리하면서 Promise 배열을 받아서 코드의 가장 아래에서 Promise.all을 사용해 모든 작업이 끝날 때까지 대기했다. 그런데 이 부분에서 작업이 다 끝나지 않았는데도 기다리지 않고 코드가 끝나버려 문제가 발생한 것이었다. 그래서 모든 Promise를 마지막에 한꺼번에 기다리지 않고, 묶음 단위로 나눠서 기다리는 방식으로 변경했다.
게시물 관리
게시물 관리 기능은 처음에 user와 같이 검색기능을 구현했다가 조금 더 세분화해서 상품을 검색하면 좋을 것 같다고 생각이 들어서 유저의 닉네임과 상품 제목, 가격으로 상세하게 상품을 검색할 수 있다.
검색 API
import { NextResponse } from 'next/server';
import { connectDB } from '@/lib/mongodb';
import getUserSession from '@/lib/getUserSession';
export async function GET(req) {
try {
const session = await getUserSession();
if (!session.admin) return NextResponse.json({ message: 'Not authenticated' }, { status: 401 });
const { searchParams } = new URL(req.url, process.env.NEXTAUTH_URL);
const offset = parseInt(searchParams.get('offset')) || 0;
const limit = parseInt(searchParams.get('limit')) || 10;
const nickname = searchParams.get('nickname');
const keyword = searchParams.get('keyword');
const price = searchParams.get('price');
const client = await connectDB;
const db = client.db(process.env.MONGODB_NAME);
let searchQuery = {};
if (nickname) {
const user = await db.collection('users').findOne({
nickname: { $regex: nickname, $options: 'i' },
});
if (!user) {
return NextResponse.json({ message: 'User not found' }, { status: 404 });
}
if (keyword) {
searchQuery = {
userId: user._id,
$or: [
{ title: { $regex: keyword, $options: 'i' } },
{ tags: { $elemMatch: { $regex: keyword, $options: 'i' } } },
],
};
} else {
searchQuery.userId = user._id;
}
} else if (keyword) {
searchQuery = {
$or: [
{ title: { $regex: keyword, $options: 'i' } },
{ tags: { $elemMatch: { $regex: keyword, $options: 'i' } } },
],
};
}
if (price) {
const priceConditions = price
.split(' ')
.map(condition => {
const operator = condition[0];
const value = parseInt(condition.substring(1), 10);
if (operator === '>') {
return { price: { $gte: value } };
} else if (operator === '<') {
return { price: { $lte: value } };
} else {
return null;
}
})
.filter(Boolean);
if (priceConditions.length > 0) {
searchQuery.$and = (searchQuery.$and || []).concat(priceConditions);
}
}
const pipeline = [
{ $match: searchQuery },
{ $skip: offset },
{ $limit: limit },
{
$lookup: {
from: 'users',
localField: 'userId',
foreignField: '_id',
as: 'userInfo',
},
},
{
$addFields: {
nickname: { $arrayElemAt: ['$userInfo.nickname', 0] },
},
},
{
$project: {
userInfo: 0,
},
},
];
const products = await db.collection('products').aggregate(pipeline).toArray();
const total = await db.collection('products').countDocuments(searchQuery);
return NextResponse.json(
{ products, total },
{
status: 200,
},
);
} catch (error) {
return NextResponse.json(error, { status: 500 });
}
}
상단에 Taskbar는 스크롤을 했을 때 다시 최상단으로 올리지 않고 바로 사용할 수 있게 sticky를 사용해서 스크롤되다가 고정이 되게 했다.
'KEYNUT 프로젝트' 카테고리의 다른 글
[ JavaScript ] 핀치 줌 구현하기! (0) | 2024.07.19 |
---|---|
[ Next.JS ]상품 업로드, 수정, 조회 로직 (0) | 2024.07.16 |
[ Next.JS ] 효율적인 서버사이드 데이터 캐싱 및 재검증 (0) | 2024.07.13 |
HTTP ERROR 431 : failed to load resource the server responded with a status of 431 () 해결 (1) | 2024.07.08 |
cache에 대해서 (0) | 2024.06.30 |