diff --git a/.env.development b/.env.development index 53e90fd..c8a3ab5 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,7 @@ # 모바일 디바이스로 로컬 서버 확인하려면 자신 IP 주소로 변경 # 다시 로컬에서 개발할때는 localhost로 변경 -NEXT_PUBLIC_API_URL=http://localhost:3000 \ No newline at end of file +#route handler +NEXT_PUBLIC_API_URL=http://localhost:3000 + +#qsp 로그인 api +NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 \ No newline at end of file diff --git a/.env.production b/.env.production index f5f1630..1d169db 100644 --- a/.env.production +++ b/.env.production @@ -1 +1,5 @@ -NEXT_PUBLIC_API_URL=http://172.30.1.35:3000 \ No newline at end of file +#route handler +NEXT_PUBLIC_API_URL=http://172.30.1.35:3000 + +#qsp 로그인 api +NEXT_PUBLIC_QSP_API_URL=http://1.248.227.176:8120 \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index 24e03eb..1a3917a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -6,14 +6,18 @@ const nextConfig: NextConfig = { sassOptions: { includePaths: [path.join(__dirname, './src/styles')], }, - // async rewrites() { - // return [ - // { - // source: '/api/:path*', - // destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, - // }, - // ] - // }, + async rewrites() { + return [ + { + source: '/api/user/login', + destination: `${process.env.NEXT_PUBLIC_QSP_API_URL}/api/user/login`, + }, + { + source: '/api/:path*', + destination: `${process.env.NEXT_PUBLIC_API_URL}/api/:path*`, + }, + ] + }, } export default nextConfig diff --git a/src/api/suitable.ts b/src/api/suitable.ts deleted file mode 100644 index e8458c4..0000000 --- a/src/api/suitable.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { database } from '@/data' -import { axiosInstance } from '@/libs/axios' - -export interface Suitable { - id?: number - product_name: string - manufacturer: string - roof_material: string - shape: string - support_roof_tile: string - support_roof_tile_memo: string - support_roof_bracket: string - support_roof_bracket_memo: string - yg_anchor: string - yg_anchor_memo: string - rg_roof_tile_part: string - rg_roof_tile_part_memo: string - dido_hunt_support_tile_2: string - dido_hunt_support_tile_2_memo: string - takashima_power_base: string - takashima_power_base_memo: string - takashima_tile_bracket: string - takashima_tile_bracket_memo: string - slate_bracket_4: string - slate_bracket_4_memo: string - slate_single_metal_bracket: string - slate_single_metal_bracket_memo: string - dido_hunt_short_rack_4: string - dido_hunt_short_rack_4_memo: string - takashima_slate_bracket_slate_single: string - takashima_slate_bracket_slate_single_memo: string - df_metal_bracket: string - df_metal_bracket_memo: string - slate_metal_bracket: string - slate_metal_bracket_memo: string - takashima_slate_bracket_metal_roof: string - takashima_slate_bracket_metal_roof_memo: string -} - -export const suitableApi = { - getList: async (): Promise => { - const response = await axiosInstance.get('/api/suitable/list') - console.log('🚀 ~ getList: ~ response:', response) - return response.data - }, - - getDetails: async (roofMaterial: string): Promise => { - const response = await axiosInstance.get(`/api/suitable/details?roof-material=${roofMaterial}`) - return response.data - }, - - create: async () => { - const suitableData: Suitable[] = [] - - database.forEach((item) => { - suitableData.push({ - product_name: item[0], - manufacturer: item[1], - roof_material: item[2], - shape: item[3], - support_roof_tile: item[4], - support_roof_tile_memo: item[5], - support_roof_bracket: item[6], - support_roof_bracket_memo: item[7], - yg_anchor: item[8], - yg_anchor_memo: item[9], - rg_roof_tile_part: item[10], - rg_roof_tile_part_memo: item[11], - dido_hunt_support_tile_2: item[12], - dido_hunt_support_tile_2_memo: item[13], - takashima_power_base: item[14], - takashima_power_base_memo: item[15], - takashima_tile_bracket: item[16], - takashima_tile_bracket_memo: item[17], - slate_bracket_4: item[18], - slate_bracket_4_memo: item[19], - slate_single_metal_bracket: item[20], - slate_single_metal_bracket_memo: item[21], - dido_hunt_short_rack_4: item[22], - dido_hunt_short_rack_4_memo: item[23], - takashima_slate_bracket_slate_single: item[24], - takashima_slate_bracket_slate_single_memo: item[25], - df_metal_bracket: item[26], - df_metal_bracket_memo: item[27], - slate_metal_bracket: item[28], - slate_metal_bracket_memo: item[29], - takashima_slate_bracket_metal_roof: item[30], - takashima_slate_bracket_metal_roof_memo: item[31], - }) - }) - - const response = await axiosInstance.post('/api/suitable', suitableData) - return response.data - }, -} diff --git a/src/app/@header/default.tsx b/src/app/@header/default.tsx index 33c708b..aff8409 100644 --- a/src/app/@header/default.tsx +++ b/src/app/@header/default.tsx @@ -1,5 +1,5 @@ import Header from '@/components/ui/common/Header' -export default function page() { +export default async function page() { return
} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..390f8d1 --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,15 @@ +import { sessionOptions } from '@/libs/session' +import { SessionData } from '@/types/Auth' +import { getIronSession } from 'iron-session' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export async function GET(request: Request) { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + session.destroy() + // return redirect('/login') + + return NextResponse.json({ code: 200, message: 'Logout successful' }) +} diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts new file mode 100644 index 0000000..8cbbc1a --- /dev/null +++ b/src/app/api/auth/route.ts @@ -0,0 +1,58 @@ +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +import { getIronSession } from 'iron-session' +import { axiosInstance } from '@/libs/axios' +import { sessionOptions } from '@/libs/session' + +import type { SessionData } from '@/types/Auth' + +export async function POST(request: Request) { + const { loginId, pwd } = await request.json() + + const result = await axiosInstance(`${process.env.NEXT_PUBLIC_QSP_API_URL}`).post(`/api/user/login`, { + loginId, + pwd, + }) + console.log('🚀 ~ result ~ result:', result) + + if (result.data.result.code === 200) { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + console.log('start session edit!') + session.langCd = result.data.data.langCd + session.currPage = result.data.data.currPage + session.rowCount = result.data.data.rowCount + session.startRow = result.data.data.startRow + session.endRow = result.data.data.endRow + session.compCd = result.data.data.compCd + session.agencyStoreId = result.data.data.agencyStoreId + session.storeId = result.data.data.storeId + session.userId = result.data.data.userId + session.category = result.data.data.category + session.userNm = result.data.data.userNm + session.userNmKana = result.data.data.userNmKana + session.telNo = result.data.data.telNo + session.fax = result.data.data.fax + session.email = result.data.data.email + session.lastEditUser = result.data.data.lastEditUser + session.storeGubun = result.data.data.storeGubun + session.pwCurr = result.data.data.pwCurr + session.pwdInitYn = result.data.data.pwdInitYn + session.apprStatCd = result.data.data.apprStatCd + session.loginFailCnt = result.data.data.loginFailCnt + session.loginFailMinYn = result.data.data.loginFailMinYn + session.priceViewStatCd = result.data.data.priceViewStatCd + session.groupId = result.data.data.groupId + session.storeLvl = result.data.data.storeLvl + session.custCd = result.data.data.custCd + session.builderNo = result.data.data.builderNo + session.isLoggedIn = true + console.log('end session edit!') + + await session.save() + } + + return NextResponse.json({ code: 200, message: 'Login is Succecss!!' }) +} diff --git a/src/app/page.tsx b/src/app/page.tsx index ac97ef3..b803534 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,14 @@ import Main from '@/components/ui/Main' +import { sessionOptions } from '@/libs/session' +import type { SessionData } from '@/types/Auth' +import { getIronSession } from 'iron-session' +import { cookies } from 'next/headers' export default async function Home() { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + console.log('🚀 ~ Home ~ session:', session) + return ( <>
diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 8f6e373..66a3f0c 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,16 +1,69 @@ 'use client' -import { useState } from 'react' +import { useReducer, useState } from 'react' +import { useRouter } from 'next/navigation' +import { useQuery } from '@tanstack/react-query' +import { axiosInstance } from '@/libs/axios' + +interface AccountState { + loginId: string + pwd: string +} export default function Login() { - const [pwShow, setPwShow] = useState(false) //비밀번호 보이기 숨기기 + const router = useRouter() + //비밀번호 보이기 숨기기 + const [pwShow, setPwShow] = useState(false) + //ID 저장 체크박스 + const [idSave, setIdSave] = useState(false) + //Q.PARTNERS 체크박스 + const [isPartners, setIsPartners] = useState(false) + //로그인 상태 + const [isLogin, setIsLogin] = useState(false) + + const reducer = (state: AccountState, newState: Partial) => ({ ...state, ...newState }) + const [account, setAccount] = useReducer(reducer, { + loginId: '', + pwd: '', + }) + + interface LoginData { + code: number + message: string | null + } + + const { + data: loginData, + isPending, + error, + } = useQuery({ + queryKey: ['login', 'account'], + queryFn: async () => { + const { data } = await axiosInstance('').post(`/api/auth`, { + loginId: account.loginId, + pwd: account.pwd, + }) + router.push('/') + + return data + }, + enabled: isLogin, + staleTime: 0, + retry: false, + }) return ( <>
- + setAccount({ loginId: e.target.value })} + /> @@ -18,7 +71,13 @@ export default function Login() {
- + setAccount({ pwd: e.target.value })} + /> @@ -26,19 +85,19 @@ export default function Login() {
- + setIdSave(!idSave)} />
Q.PARTNERS
-
diff --git a/src/components/ui/Main.tsx b/src/components/ui/Main.tsx index a9eb336..37de8e7 100644 --- a/src/components/ui/Main.tsx +++ b/src/components/ui/Main.tsx @@ -1,16 +1,27 @@ 'use client' import { useHeaderStore } from '@/store/header' -import { useRouter } from 'next/navigation' +import { useSideNavState } from '@/store/sideNavState' +import { usePathname, useRouter } from 'next/navigation' import { useEffect } from 'react' export default function Main() { const router = useRouter() + const pathname = usePathname() const { setBackBtn } = useHeaderStore() + const { reset } = useSideNavState() + /** + * 헤더 뒤로가기 버튼 컨트롤 + * 사이드바 초기화 컨트롤 + */ useEffect(() => { - setBackBtn(false) - }, []) + if (pathname === '/') { + setBackBtn(false) + } + //사이드바 초기화 + reset() + }, [pathname]) return ( <> diff --git a/src/components/ui/common/Header.tsx b/src/components/ui/common/Header.tsx index 580545d..443e078 100644 --- a/src/components/ui/common/Header.tsx +++ b/src/components/ui/common/Header.tsx @@ -1,16 +1,17 @@ 'use client' -import { useEffect, useState } from 'react' - import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import { Swiper, SwiperSlide } from 'swiper/react' +import { useSideNavState } from '@/store/sideNavState' +import { useHeaderStore } from '@/store/header' + import type { HeaderProps } from '@/types/Header' import 'swiper/css' -import { useSideNavState } from '@/store/sideNavState' +import { axiosInstance } from '@/libs/axios' // type HeaderProps = { // name: string //header 이름 @@ -20,27 +21,26 @@ import { useSideNavState } from '@/store/sideNavState' export default function Header({ name }: HeaderProps) { const router = useRouter() const pathname = usePathname() - const { sideNavIsOpen, setSideNavIsOpen, reset } = useSideNavState() - const [isShowBackBtn, setIsShowBackBtn] = useState(false) + const { sideNavIsOpen, setSideNavIsOpen } = useSideNavState() + const { backBtn } = useHeaderStore() if (pathname === '/login') { return null } - useEffect(() => { - if (pathname !== '/') { - setIsShowBackBtn(true) + const handleLogout = async () => { + const { data } = await axiosInstance(null).get('/api/auth/logout') + if (data.code === 200) { + router.push('/login') } - //사이드바 초기화 - reset() - }, [pathname]) + } return ( <>
- {isShowBackBtn && ( + {backBtn && (
@@ -112,7 +112,9 @@ export default function Header({ name }: HeaderProps) {
  • - +
  • diff --git a/src/libs/axios.ts b/src/libs/axios.ts index 4fa4554..5f1b076 100644 --- a/src/libs/axios.ts +++ b/src/libs/axios.ts @@ -1,16 +1,18 @@ import axios from 'axios' -const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000' +export const axiosInstance = (url: string | null | undefined) => { + const baseURL = url || process.env.NEXT_PUBLIC_API_URL -export const axiosInstance = axios.create({ - baseURL, - headers: { - 'Content-Type': 'application/json', - }, -}) + return axios.create({ + baseURL, + headers: { + Accept: 'application/json', + }, + }) +} // Request interceptor -axiosInstance.interceptors.request.use( +axios.interceptors.request.use( (config) => { // 여기에 토큰 추가 등의 공통 로직을 넣을 수 있습니다 return config @@ -21,10 +23,44 @@ axiosInstance.interceptors.request.use( ) // Response interceptor -axiosInstance.interceptors.response.use( - (response) => response, +axios.interceptors.response.use( + (response) => transferResponse(response), (error) => { // 에러 처리 로직 return Promise.reject(error) }, ) + +// response데이터가 array, object에 따라 분기하여 키 변환 +const transferResponse = (response: any) => { + if (!response.data) return response.data + + // 배열인 경우 각 객체의 키를 변환 + if (Array.isArray(response.data)) { + return response.data.map((item: any) => transformObjectKeys(item)) + } + + // 단일 객체인 경우 + return transformObjectKeys(response.data) +} + +// camel case object 반환 +const transformObjectKeys = (obj: any): any => { + if (Array.isArray(obj)) { + return obj.map(transformObjectKeys) + } + + if (obj !== null && typeof obj === 'object') { + return Object.keys(obj).reduce((acc: any, key: string) => { + const camelKey = snakeToCamel(key) + acc[camelKey] = transformObjectKeys(obj[key]) + return acc + }, {}) + } + + return obj +} +// snake case to camel case +const snakeToCamel = (str: string): string => { + return str.replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace('-', '').replace('_', '')) +} diff --git a/src/libs/session.ts b/src/libs/session.ts index a61c755..21a0479 100644 --- a/src/libs/session.ts +++ b/src/libs/session.ts @@ -1,5 +1,7 @@ +import type { SessionData } from '@/types/Auth' import { getIronSession, SessionOptions } from 'iron-session' import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' export const sessionOptions: SessionOptions = { cookieName: 'onsitesurvey_session', @@ -14,15 +16,34 @@ export const sessionOptions: SessionOptions = { }, } -export interface SessionData { - username: string | null - email: string | null - isLoggedIn: boolean -} - export const defaultSession: SessionData = { - username: '', - email: '', + langCd: null, + currPage: 0, + rowCount: 0, + startRow: 0, + endRow: 0, + compCd: null, + agencyStoreId: null, + storeId: null, + userId: null, + category: null, + userNm: null, + userNmKana: null, + telNo: null, + fax: null, + email: null, + lastEditUser: null, + storeGubun: null, + pwCurr: null, + pwdInitYn: null, + apprStatCd: null, + loginFailCnt: null, + loginFailMinYn: null, + priceViewStatCd: null, + groupId: null, + storeLvl: null, + custCd: null, + builderNo: null, isLoggedIn: false, } @@ -31,3 +52,11 @@ export const getSession = async () => { const session = await getIronSession(cookieStore, sessionOptions) return session } + +export const logout = async () => { + const cookieStore = await cookies() + const session = await getIronSession(cookieStore, sessionOptions) + + session.destroy() + return redirect('/login') +} diff --git a/src/middleware.ts b/src/middleware.ts index 5fe31f3..1bcfd96 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,7 +2,8 @@ import { NextResponse } from 'next/server' import { cookies } from 'next/headers' import type { NextRequest } from 'next/server' import { getIronSession } from 'iron-session' -import { SessionData, sessionOptions } from './libs/session' +import { sessionOptions } from './libs/session' +import type { SessionData } from './types/Auth' export async function middleware(request: NextRequest) { const cookieStore = await cookies() diff --git a/src/types/Auth.ts b/src/types/Auth.ts new file mode 100644 index 0000000..8920afc --- /dev/null +++ b/src/types/Auth.ts @@ -0,0 +1,30 @@ +export interface SessionData { + langCd: null + currPage: Number | null + rowCount: Number | null + startRow: Number | null + endRow: Number | null + compCd: null + agencyStoreId: null + storeId: null + userId: null + category: null + userNm: null + userNmKana: null + telNo: null + fax: null + email: null + lastEditUser: null + storeGubun: null + pwCurr: null + pwdInitYn: null + apprStatCd: null + loginFailCnt: Number | null + loginFailMinYn: null + priceViewStatCd: null + groupId: null + storeLvl: null + custCd: null + builderNo: null + isLoggedIn: boolean +}