Skip to content

Commit

Permalink
관리자 페이지 구현 - 메인, 도시 관리 페이지 (#789)
Browse files Browse the repository at this point in the history
* feat: 행록 디자인 시스템 적용 및 layout 생성

* feature: 예외 페이지 추가

* feat: SidebarNavigation 구현

* feat: AdminMainPage 구현

* refactor: SidebarNavigation 리팩터링

* feat: page routing 구현

* feat: 도시 테이블 구현

* feat: city table style 적용

* feat: 도시 추가 모달 구현

* feat: 도시 수정 버튼 및 기능 구현

* refactor: 주석 제거
  • Loading branch information
LJW25 authored Feb 2, 2024
1 parent 943b200 commit 78c0d36
Show file tree
Hide file tree
Showing 151 changed files with 4,404 additions and 2,939 deletions.
3,936 changes: 1,773 additions & 2,163 deletions frontend-monorepo/.pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 7 additions & 3 deletions frontend-monorepo/packages/hanglog-admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
"@emotion/css": "^11.11.2",
"@emotion/react": "^11.11.3",
"@tanstack/react-query": "^5.17.19",
"hang-log-design-system": "workspace:^",
"axios": "^1.6.7",
"hang-log-design-system": "^1.3.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.3",
"recoil": "^0.7.7",
"storybook": "^7.6.10",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.1"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.8",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
Expand All @@ -36,6 +39,7 @@
"@storybook/react": "^7.6.10",
"@storybook/react-vite": "^7.6.10",
"@storybook/test": "^7.6.10",
"@types/babel__core": "^7",
"@types/babel__preset-env": "^7",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
Expand All @@ -51,9 +55,9 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-storybook": "^0.6.15",
"msw": "1.2.3",
"msw": "^2.1.5",
"prettier": "3.2.4",
"typescript": "*",
"typescript": "4.9.5",
"vite": "^5.0.8"
},
"msw": {
Expand Down
170 changes: 77 additions & 93 deletions frontend-monorepo/packages/hanglog-admin/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
/* tslint:disable */

/**
* Mock Service Worker (1.2.3).
* Mock Service Worker (2.1.5).
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/

const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'
const INTEGRITY_CHECKSUM = '223d191a56023cd36aa88c802961b911'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

self.addEventListener('install', function () {
Expand Down Expand Up @@ -86,12 +87,6 @@ self.addEventListener('message', async function (event) {

self.addEventListener('fetch', function (event) {
const { request } = event
const accept = request.headers.get('accept') || ''

// Bypass server-sent events.
if (accept.includes('text/event-stream')) {
return
}

// Bypass navigation requests.
if (request.mode === 'navigate') {
Expand All @@ -112,29 +107,8 @@ self.addEventListener('fetch', function (event) {
}

// Generate unique request ID.
const requestId = Math.random().toString(16).slice(2)

event.respondWith(
handleRequest(event, requestId).catch((error) => {
if (error.name === 'NetworkError') {
console.warn(
'[MSW] Successfully emulated a network error for the "%s %s" request.',
request.method,
request.url,
)
return
}

// At this point, any exception indicates an issue with the original request/response.
console.error(
`\
[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`,
request.method,
request.url,
`${error.name}: ${error.message}`,
)
}),
)
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})

async function handleRequest(event, requestId) {
Expand All @@ -146,21 +120,24 @@ async function handleRequest(event, requestId) {
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const clonedResponse = response.clone()
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body:
clonedResponse.body === null ? null : await clonedResponse.text(),
headers: Object.fromEntries(clonedResponse.headers.entries()),
redirected: clonedResponse.redirected,
const responseClone = response.clone()

sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
})
[responseClone.body],
)
})()
}

Expand Down Expand Up @@ -196,20 +173,20 @@ async function resolveMainClient(event) {

async function getResponse(event, client, requestId) {
const { request } = event
const clonedRequest = request.clone()

// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()

function passthrough() {
// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const headers = Object.fromEntries(clonedRequest.headers.entries())
const headers = Object.fromEntries(requestClone.headers.entries())

// Remove MSW-specific request headers so the bypassed requests
// comply with the server's CORS preflight check.
// Operate with the headers as an object because request "Headers"
// are immutable.
delete headers['x-msw-bypass']
// Remove internal MSW request header so the passthrough request
// complies with any potential CORS preflight checks on the server.
// Some servers forbid unknown request headers.
delete headers['x-msw-intention']

return fetch(clonedRequest, { headers })
return fetch(requestClone, { headers })
}

// Bypass mocking when the client is not active.
Expand All @@ -227,31 +204,36 @@ async function getResponse(event, client, requestId) {

// Bypass requests with the explicit bypass header.
// Such requests can be issued by "ctx.fetch()".
if (request.headers.get('x-msw-bypass') === 'true') {
const mswIntention = request.headers.get('x-msw-intention')
if (['bypass', 'passthrough'].includes(mswIntention)) {
return passthrough()
}

// Notify the client that a request has been intercepted.
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.text(),
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
})
[requestBuffer],
)

switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
Expand All @@ -261,21 +243,12 @@ async function getResponse(event, client, requestId) {
case 'MOCK_NOT_FOUND': {
return passthrough()
}

case 'NETWORK_ERROR': {
const { name, message } = clientMessage.data
const networkError = new Error(message)
networkError.name = name

// Rejecting a "respondWith" promise emulates a network error.
throw networkError
}
}

return passthrough()
}

function sendToClient(client, message) {
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()

Expand All @@ -287,17 +260,28 @@ function sendToClient(client, message) {
resolve(event.data)
}

client.postMessage(message, [channel.port2])
client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}

function sleep(timeMs) {
return new Promise((resolve) => {
setTimeout(resolve, timeMs)
async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}

const mockedResponse = new Response(response.body, response)

Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})
}

async function respondWithMock(response) {
await sleep(response.delay)
return new Response(response.body, response)
return mockedResponse
}
12 changes: 10 additions & 2 deletions frontend-monorepo/packages/hanglog-admin/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { Outlet } from 'react-router-dom';

import Header from '@components/layout/Header/Header';
import Footer from '@components/layout/Footer/Footer';
import ToastContainer from '@components/common/ToastContainer/ToastContainer';

const App = () => {
return (
<div>
<Outlet />
<p>App Component</p>
<Header />
<main>
<Outlet />
</main>
<Footer />
<ToastContainer />
</div>
);
};
Expand Down
15 changes: 15 additions & 0 deletions frontend-monorepo/packages/hanglog-admin/src/api/HTTPError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class HTTPError extends Error {
statusCode: number;

code?: number;

constructor(statusCode: number, message?: string, code?: number) {
super(message);

this.name = 'HTTPError';
this.statusCode = statusCode;
this.code = code;

Object.setPrototypeOf(this, HTTPError.prototype);
}
}
14 changes: 14 additions & 0 deletions frontend-monorepo/packages/hanglog-admin/src/api/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import axios from 'axios';

import { handleAPIError } from '@api/interceptors';

import { AXIOS_BASE_URL, NETWORK } from '@constants/api';

export const axiosInstance = axios.create({
baseURL: AXIOS_BASE_URL,
timeout: NETWORK.TIMEOUT,
withCredentials: true,
useAuth: true,
});

axiosInstance.interceptors.response.use((response) => response, handleAPIError);
10 changes: 10 additions & 0 deletions frontend-monorepo/packages/hanglog-admin/src/api/city/getCity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { axiosInstance } from '@api/axiosInstance';

import type { CityData } from '@type/city';

import { END_POINTS } from '@constants/api';

export const getCity = async () => {
const { data } = await axiosInstance.get<CityData[]>(END_POINTS.CITY);
return data;
};
13 changes: 13 additions & 0 deletions frontend-monorepo/packages/hanglog-admin/src/api/city/postCity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { axiosInstance } from '@api/axiosInstance';

import type { CityFormData } from '@type/city';

import { END_POINTS } from '@constants/api';

export const postCity = async (cityFormData: CityFormData) => {
const response = await axiosInstance.post(END_POINTS.CITY, cityFormData);

const tripId = response.headers.location.replace(`${END_POINTS.CITY}/`, '');

return tripId;
};
15 changes: 15 additions & 0 deletions frontend-monorepo/packages/hanglog-admin/src/api/city/putCity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { axiosInstance } from '@api/axiosInstance';

import type { CityFormData } from '@type/city';

import { END_POINTS } from '@constants/api';

export interface PutCityParams extends CityFormData {
cityId: number;
}

export const putCity = ({ cityId, ...cityInformation }: PutCityParams) => {
return axiosInstance.put<CityFormData>(END_POINTS.CHANGE_CITY(cityId), {
...cityInformation,
});
};
7 changes: 7 additions & 0 deletions frontend-monorepo/packages/hanglog-admin/src/api/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'axios';

declare module 'axios' {
export interface AxiosRequestConfig {
useAuth?: boolean;
}
}
Loading

0 comments on commit 78c0d36

Please sign in to comment.