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
42 changes: 42 additions & 0 deletions apps/admin/app/actions/authActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use server';

import { instance } from '@withbee/apis';
import { signIn, signOut } from '../../auth';
import { AuthError } from 'next-auth';
import { auth, logout } from '@withbee/auth-config';
import { LogoutRequest } from '@withbee/types';

export const handleCredentialsSignin = async ({
email,
password,
}: {
email: string;
password: string;
}) => {
try {
await signIn('credentials', {
email,
password,
redirect: false,
});
} catch (error) {
console.error('Error occurred during credentials signin:', error);
if (error instanceof AuthError) {
switch (error.type) {
case 'CredentialsSignin':
return { error: 'Invalid credentials' };
default:
return { error: 'Something went wrong' };
}
}
return { error: 'Unexpected error occurred' };
}
};

export const handleSignOut = async (
accessToken: string,
refreshToken: string,
) => {
await signOut();
await logout({ accessToken, refreshToken });
};
3 changes: 3 additions & 0 deletions apps/admin/app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { handlers } from '../../../../auth';

export const { GET, POST } = handlers;
4 changes: 2 additions & 2 deletions apps/admin/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const geistMono = localFont({
});

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
title: 'WithbeeTravel Admin',
description: 'Generated by Withbee from 우리FISA',
};

export default function RootLayout({
Expand Down
11 changes: 5 additions & 6 deletions apps/admin/app/login-logs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ const AdminPage = () => {
const [currentPage, setCurrentPage] = useState<number>(1);
const [totalPages, setTotalPages] = useState<number>(0);
const [loginLogType, setLoginLogType] = useState<string>('ALL');
const [userId, setUserId] = useState<number>();
const [userId, setUserId] = useState<string>('');

const fetchLoginLogs = async (
page: number,
logType: string,
userId: number,
userId: string,
) => {
setLoading(true);
try {
const response = await getLoginLogs(page, 5, logType, userId);
const response = await getLoginLogs(page, 5, logType, Number(userId));
console.log('response', response);
if ('data' in response && response.data) {
setLoginLogs(response.data.content);
setPageable(response.data.pageable);
Expand Down Expand Up @@ -96,9 +97,7 @@ const AdminPage = () => {
<button onClick={handleSearch}>검색</button>
</div>

{loading ? (
<p>로딩 중...</p>
) : (
{loginLogs.length !== 0 && (
<div>
<table className={styles.table}>
<thead>
Expand Down
106 changes: 106 additions & 0 deletions apps/admin/app/login/page.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
.loginWrapper {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
}

.loginCard {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}

.loginHeader {
padding: 1.5rem;
text-align: center;
}

.loginHeader h1 {
font-size: 1.5rem;
font-weight: bold;
margin: 0;
}

.loginContent {
padding: 1.5rem;
}

.formGroup {
margin-bottom: 1rem;
}

.formGroup label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
}

.inputContainer {
position: relative;
}

.inputIcon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
color: #9ca3af;
}

.inputContainer input {
width: 100%;
padding: 0.75rem;
padding-left: 2.5rem;
border: 1px solid #e5e7eb;
border-radius: 4px;
font-size: 0.875rem;
}

.inputContainer input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2);
}

.loginButton {
width: 100%;
padding: 0.75rem;
background-color: #2563eb;
color: white;
border: none;
border-radius: 4px;
font-size: 0.875rem;
cursor: pointer;
transition: background-color 0.2s;
}

.loginButton:hover {
background-color: #1d4ed8;
}

.loginFooter {
padding: 1.5rem;
text-align: center;
border-top: 1px solid #e5e7eb;
}

.loginFooter p {
font-size: 0.875rem;
color: #6b7280;
}

.loginFooter a {
color: #2563eb;
text-decoration: none;
}

.loginFooter a:hover {
text-decoration: underline;
}
79 changes: 79 additions & 0 deletions apps/admin/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client';

import React, { useState } from 'react';
import { Lock, User } from 'lucide-react';
import styles from './page.module.css';
import { handleCredentialsSignin } from '../actions/authActions';

const AdminLogin = () => {
const [formData, setFormData] = useState({
email: '',
password: '',
});

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// 로그인 로직 구현
console.log('Login attempt:', formData);
await handleCredentialsSignin({
email: formData.email,
password: formData.password,
});

// 로그인 성공 시 리다이렉트
window.location.href = '/';
};

return (
<div className={styles.loginWrapper}>
<div className={styles.loginCard}>
<div className={styles.loginHeader}>
<h1>관리자 로그인</h1>
</div>
<div className={styles.loginContent}>
<form onSubmit={handleSubmit}>
<div className={styles.formGroup}>
<label htmlFor="email">아이디</label>
<div className={styles.inputContainer}>
<User className={styles.inputIcon} />
<input
id="email"
placeholder="아이디를 입력하세요"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
/>
</div>
</div>
<div className={styles.formGroup}>
<label htmlFor="password">비밀번호</label>
<div className={styles.inputContainer}>
<Lock className={styles.inputIcon} />
<input
id="password"
type="password"
placeholder="비밀번호를 입력하세요"
value={formData.password}
onChange={(e) =>
setFormData({ ...formData, password: e.target.value })
}
/>
</div>
</div>
<button type="submit" className={styles.loginButton}>
로그인
</button>
</form>
</div>
<div className={styles.loginFooter}>
<p>
비밀번호를 잊으셨나요? <a href="#">비밀번호 찾기</a>
</p>
</div>
</div>
</div>
);
};

export default AdminLogin;
4 changes: 2 additions & 2 deletions apps/admin/app/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
}
.mainContent {
flex: 1;
padding: 2rem;
padding: 20px;
background-color: #ffffff;
}

Expand Down Expand Up @@ -217,4 +217,4 @@
.button:hover {
background-color: #f3f4f6;
/* hover:bg-gray-50 */
}
}
1 change: 1 addition & 0 deletions apps/admin/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { handlers, signIn, signOut, auth } from '@withbee/auth-config';
2 changes: 1 addition & 1 deletion apps/admin/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import styles from '../app/page.module.css';
const Sidebar = () => {
return (
<aside className={styles.sidebar}>
<h2 className={styles.sidebarTitle}>Admin</h2>
{/* <h2 className={styles.sidebarTitle}>Admin</h2> */}
<nav>
<ul className={styles.sidebarNav}>
<li>
Expand Down
38 changes: 38 additions & 0 deletions apps/admin/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { NextResponse, type NextRequest } from 'next/server';
import { auth } from './auth';

export default async function middleware(
request: NextRequest,
response: NextResponse,
) {
// console.log('미들웨어 실행', request.url);
const session = await auth();

// console.log('미들웨어 세션', session);
if (
!session ||
!session.user?.accessToken ||
session.error === 'RefreshAccessTokenError'
) {
// signOut();
const response = NextResponse.redirect(new URL('/login', request.url));

// response.cookies.delete('next-auth.session-token');
// response.cookies.delete('next-auth.csrf-token');
// response.cookies.delete('next-auth.callback-url');

return response;
}

return NextResponse.next();
}

// 미들웨어가 적용될 경로 설정
export const config = {
matcher: [
'/login-logs/:path*',
'/travel-management/:path*',
'/user-management/:path*',
'/',
],
};
29 changes: 29 additions & 0 deletions apps/admin/next-auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'next-auth';
import { User } from 'next-auth';
import 'next-auth/jwt';

declare module 'next-auth' {
interface User {
accessToken?: string;
refreshToken?: string;
role?: string;
}

interface Session {
user: User & {
expires_at?: number;
};
error?: 'RefreshAccessTokenError';
}
}

declare module 'next-auth/jwt' {
interface JWT {
accessToken?: string;
expires_at?: number;
refreshToken?: string;
error?: 'RefreshAccessTokenError';
role?: string;
user?: User;
}
}
Loading
Loading