issue #
All checks were successful
Test CI / build (push) Successful in 28s

- DTO 패키지 레지스트리 전환 중
This commit is contained in:
geonhee-min
2025-12-16 17:26:58 +09:00
parent 4a3896a313
commit 60e9d2a631
13 changed files with 512 additions and 189 deletions

265
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "scheduler",
"version": "0.0.0",
"dependencies": {
"@baekyangdan/core-utils": "^1.0.9",
"@baekyangdan/core-utils": "^1.0.21",
"@diceui/mention": "^0.8.0",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
@@ -40,6 +40,8 @@
"@tailwindcss/cli": "^4.1.16",
"@tailwindcss/vite": "^4.1.16",
"axios": "^1.13.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -373,11 +375,14 @@
}
},
"node_modules/@baekyangdan/core-utils": {
"version": "1.0.9",
"resolved": "https://gitea.bkdhome.p-e.kr/api/packages/baekyangdan/npm/%40baekyangdan%2Fcore-utils/-/1.0.9/core-utils-1.0.9.tgz",
"integrity": "sha512-zeXQPXJlwpO2/PzmQJQrXP9A6/maZmWWISmEuW82R42fkgXwaxavT/xlI1MsYLg1tqibwNBLSgnjXd0H9vR0KQ==",
"version": "1.0.21",
"resolved": "https://gitea.bkdhome.p-e.kr/api/packages/baekyangdan/npm/%40baekyangdan%2Fcore-utils/-/1.0.21/core-utils-1.0.21.tgz",
"integrity": "sha512-LYkzavYnforDtXm/icOg6rQkRAQAgpdwlC6w8dpWAz/N7ynIHdUHJZSPRRsQc9Jy3hQp7+vtKjZI4LP3NKC0UA==",
"license": "ISC",
"dependencies": {
"@swc/core": "^1.15.5",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"date-fns": "^4.1.0",
"reflect-metadata": "^0.2.2",
"tsup": "^8.5.1"
@@ -3121,6 +3126,220 @@
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
"node_modules/@swc/core": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.5.tgz",
"integrity": "sha512-VRy+AEO0zqUkwV9uOgqXtdI5tNj3y3BZI+9u28fHNjNVTtWYVNIq3uYhoGgdBOv7gdzXlqfHKuxH5a9IFAvopQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.25"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.15.5",
"@swc/core-darwin-x64": "1.15.5",
"@swc/core-linux-arm-gnueabihf": "1.15.5",
"@swc/core-linux-arm64-gnu": "1.15.5",
"@swc/core-linux-arm64-musl": "1.15.5",
"@swc/core-linux-x64-gnu": "1.15.5",
"@swc/core-linux-x64-musl": "1.15.5",
"@swc/core-win32-arm64-msvc": "1.15.5",
"@swc/core-win32-ia32-msvc": "1.15.5",
"@swc/core-win32-x64-msvc": "1.15.5"
},
"peerDependencies": {
"@swc/helpers": ">=0.5.17"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.5.tgz",
"integrity": "sha512-RvdpUcXrIz12yONzOdQrJbEnq23cOc2IHOU1eB8kPxPNNInlm4YTzZEA3zf3PusNpZZLxwArPVLCg0QsFQoTYw==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.5.tgz",
"integrity": "sha512-ufJnz3UAff/8G5OfqZZc5cTQfGtXyXVLTB8TGT0xjkvEbfFg8jZUMDBnZT/Cn0k214JhMjiLCNl0A8aY/OKsYQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.5.tgz",
"integrity": "sha512-Yqu92wIT0FZKLDWes+69kBykX97hc8KmnyFwNZGXJlbKUGIE0hAIhbuBbcY64FGSwey4aDWsZ7Ojk89KUu9Kzw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.5.tgz",
"integrity": "sha512-3gR3b5V1abe/K1GpD0vVyZgqgV+ykuB5QNecDYzVroX4QuN+amCzQaNSsVM8Aj6DbShQCBTh3hGHd2f3vZ8gCw==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.5.tgz",
"integrity": "sha512-Of+wmVh5h47tTpN9ghHVjfL0CJrgn99XmaJjmzWFW7agPdVY6gTDgkk6zQ6q4hcDQ7hXb0BGw6YFpuanBzNPow==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.5.tgz",
"integrity": "sha512-98kuPS0lZVgjmc/2uTm39r1/OfwKM0PM13ZllOAWi5avJVjRd/j1xA9rKeUzHDWt+ocH9mTCQsAT1jjKSq45bg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.5.tgz",
"integrity": "sha512-Rk+OtNQP3W/dZExL74LlaakXAQn6/vbrgatmjFqJPO4RZkq+nLo5g7eDUVjyojuERh7R2yhqNvZ/ZZQe8JQqqA==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.5.tgz",
"integrity": "sha512-e3RTdJ769+PrN25iCAlxmsljEVu6iIWS7sE21zmlSiipftBQvSAOWuCDv2A8cH9lm5pSbZtwk8AUpIYCNsj2oQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.5.tgz",
"integrity": "sha512-NmOdl6kyAw6zMz36zCdopTgaK2tcLA53NhUsTRopBc/796Fp87XdsslRHglybQ1HyXIGOQOKv2Y14IUbeci4BA==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.5.tgz",
"integrity": "sha512-EPXJRf0A8eOi8woXf/qgVIWRl9yeSl0oN1ykGZNCGI7oElsfxUobJFmpJFJoVqKFfd1l0c+GPmWsN2xavTFkNw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"license": "Apache-2.0"
},
"node_modules/@swc/types": {
"version": "0.1.25",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tailwindcss/cli": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.16.tgz",
@@ -3550,6 +3769,12 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/validator": {
"version": "13.15.10",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz",
"integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
@@ -4154,6 +4379,23 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
"license": "MIT"
},
"node_modules/class-validator": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz",
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
"license": "MIT",
"dependencies": {
"@types/validator": "^13.15.3",
"libphonenumber-js": "^1.11.1",
"validator": "^13.15.20"
}
},
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -5427,6 +5669,12 @@
"node": ">= 0.8.0"
}
},
"node_modules/libphonenumber-js": {
"version": "1.12.31",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.31.tgz",
"integrity": "sha512-Z3IhgVgrqO1S5xPYM3K5XwbkDasU67/Vys4heW+lfSBALcUZjeIIzI8zCLifY+OCzSq+fpDdywMDa7z+4srJPQ==",
"license": "MIT"
},
"node_modules/lightningcss": {
"version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
@@ -7550,6 +7798,15 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/validator": {
"version": "13.15.23",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz",
"integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vaul": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",

View File

@@ -11,7 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@baekyangdan/core-utils": "^1.0.9",
"@baekyangdan/core-utils": "^1.0.21",
"@diceui/mention": "^0.8.0",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
@@ -43,6 +43,8 @@
"@tailwindcss/cli": "^4.1.16",
"@tailwindcss/vite": "^4.1.16",
"axios": "^1.13.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",

View File

@@ -1,34 +1,14 @@
import {
CheckDuplicationRequest,
SendVerificationCodeRequest,
VerifyCodeRequest,
SignupRequest,
LoginRequest,
SendResetPasswordCodeRequest,
VerifyResetPasswordCodeRequest,
ResetPasswordRequest
} from "@/data/request";
import {
CheckDuplicationResponse,
SendVerificationCodeResponse,
VerifyCodeResponse,
SignupResponse,
LoginResponse,
SendResetPasswordCodeResponse,
VerifyResetPasswordCodeResponse,
ResetPasswordResponse
} from "@/data/response";
import { BaseNetwork } from "./BaseNetwork";
import { HttpApiUrl } from "@baekyangdan/core-utils";
import { HttpApiUrl, SchedulerDTO as DTO } from "@baekyangdan/core-utils";
const AccountApi = HttpApiUrl.Account;
export class AccountNetwork extends BaseNetwork {
private baseUrl = AccountApi.base;
async checkDuplication(data: CheckDuplicationRequest) {
async checkDuplication(data: DTO.CheckDuplicationRequest) {
const { type, value } = data;
return await this.get<CheckDuplicationResponse>(
return await this.get<DTO.CheckDuplicationResponse>(
`${this.baseUrl}${AccountApi.checkDuplication}?type=${type}&value=${value}`
, {
authPass: true
@@ -36,8 +16,8 @@ export class AccountNetwork extends BaseNetwork {
);
}
async sendVerificationCode(data: SendVerificationCodeRequest) {
return await this.post<SendVerificationCodeResponse>(
async sendVerificationCode(data: DTO.SendEmailVerificationCodeRequest) {
return await this.post<DTO.SendEmailVerificationCodeResponse>(
`${this.baseUrl}${AccountApi.sendEmailVerificationCode}`
, data
, {
@@ -46,8 +26,8 @@ export class AccountNetwork extends BaseNetwork {
);
}
async verifyCode(data: VerifyCodeRequest) {
return await this.post<VerifyCodeResponse>(
async verifyCode(data: DTO.VerifyEmailVerificationCodeRequest) {
return await this.post<DTO.VerifyEmailVerificationCodeResponse>(
`${this.baseUrl}${AccountApi.verifyEmailVerificationCode}`
, data
, {
@@ -56,8 +36,8 @@ export class AccountNetwork extends BaseNetwork {
);
}
async signup(data: SignupRequest) {
return await this.post<SignupResponse>(
async signup(data: DTO.SignupRequest) {
return await this.post<DTO.SignupResponse>(
`${this.baseUrl}${AccountApi.signup}`
, data
, {
@@ -66,8 +46,8 @@ export class AccountNetwork extends BaseNetwork {
);
}
async login(data: LoginRequest) {
return await this.post<LoginResponse>(
async login(data: DTO.LoginRequest) {
return await this.post<DTO.LoginResponse>(
`${this.baseUrl}${AccountApi.login}`
, data
, {
@@ -76,22 +56,22 @@ export class AccountNetwork extends BaseNetwork {
);
}
async sendResetPasswordCode(data: SendResetPasswordCodeRequest) {
return await this.post<SendResetPasswordCodeResponse>(
`${this.baseUrl}${AccountApi.sendResetPasswordCode}`,
async sendPasswordResetCode(data: DTO.SendPasswordResetCodeRequest) {
return await this.post<DTO.SendPasswordResetCodeResponse>(
`${this.baseUrl}${AccountApi.sendPasswordResetCode}`,
data
);
}
async verifyResetPasswordCode(data: VerifyResetPasswordCodeRequest) {
return await this.post<VerifyResetPasswordCodeResponse>(
`${this.baseUrl}${AccountApi.verifyResetPasswordCode}`,
async verifyPasswordResetCode(data: DTO.VerifyPasswordResetCodeRequest) {
return await this.post<DTO.VerifyPasswordResetCodeResponse>(
`${this.baseUrl}${AccountApi.verifyPasswordResetCode}`,
data
);
}
async resetPassword(data: ResetPasswordRequest) {
return await this.post<ResetPasswordResponse>(
async resetPassword(data: DTO.ResetPasswordRequest) {
return await this.post<DTO.ResetPasswordResponse>(
`${this.baseUrl}${AccountApi.resetPassword}`,
data
);

View File

@@ -1,15 +1,16 @@
import axios from 'axios';
import type { AuthData } from '@/data/AuthData';
import { useAuthStore } from '@/store/authStore';
import { SchedulerDTO as DTO, HttpApiUrl, UnauthorizedCode, UnauthorizedMessage } from '@baekyangdan/core-utils';
import type {
AxiosError,
AxiosInstance,
AxiosRequestConfig,
AxiosError,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
import { useAuthStore } from '@/store/authStore';
import { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse';
import type { AuthData } from '@/data/AuthData';
import { HttpApiUrl, UnauthorizedCode, UnauthorizedMessage } from '@baekyangdan/core-utils';
import axios from 'axios';
import { plainToInstance } from 'class-transformer';
import { validateOrReject } from 'class-validator';
export class BaseNetwork {
protected instance: AxiosInstance;
@@ -141,12 +142,45 @@ export class BaseNetwork {
/**
* 기본 CRUD 메서드
*/
protected async get<T = any>(url: string, config?: AxiosRequestConfig & { authPass?: boolean }) {
return await this.instance.get<T>(url, config);
protected async get<TResponse, TData extends Object = never>(
url: string,
config?: AxiosRequestConfig & { authPass?: boolean },
dtoClass?: new () => TData
) {
const result = await this.instance.get<TResponse>(url, config);
if (dtoClass && (result.data as any)?.success === true) {
const rawData = (result.data as any).data;
if (rawData) {
const instance = plainToInstance(dtoClass, rawData);
await validateOrReject(instance);
(result.data as any).data = instance;
}
}
return result.data;
}
protected async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig & { authPass?: boolean }) {
return await this.instance.post<T>(url, data, config);
protected async post<TResponse, TData extends Object = never>(
url: string,
data?: any,
config?: AxiosRequestConfig & { authPass?: boolean },
dtoClass?: new () => TData
) {
const result = await this.instance.post<TResponse>(url, data, config);
if (dtoClass && (result.data as any)?.success === true) {
const rawData = (result.data as any).data;
if (rawData) {
const instance = plainToInstance(dtoClass, rawData);
await validateOrReject(instance);
(result.data as any).data = instance;
}
}
return result.data;
}
public async refreshToken() {
@@ -167,14 +201,14 @@ export class BaseNetwork {
}
}
const result = await this.get<RefreshAccessTokenResponse>(
const result = await this.get<DTO.RefreshAccessTokenResponse>(
`${HttpApiUrl.Account.base}${HttpApiUrl.Account.refreshAccessToken}`,
{
withCredentials: true
}
);
if (!result.data.success) throw new Error;
if (!result.success || !result.data) throw new Error;
const newAccessToken = result.data.accessToken;

View File

@@ -11,26 +11,26 @@ import {
ScheduleListResponse
} from "@/data/response";
import { HttpApiUrl } from "@baekyangdan/core-utils";
import { SchedulerDTO as DTO } from "@baekyangdan/core-utils";
const ScheduleApi = HttpApiUrl.Schedule;
export class ScheduleNetwork extends BaseNetwork {
private baseUrl = ScheduleApi.base;
async getList(data: ScheduleListRequest) {
return await this.post<ScheduleListResponse>(
async getList(data: DTO.ScheduleListRequest) {
return await this.post<DTO.ScheduleListResponse>(
this.baseUrl,
data
);
}
async getDetail(id: string) {
return await this.get<ScheduleDetailResponse>(
return await this.get<DTO.ScheduleDetailResponse>(
`${this.baseUrl}/${id}`
);
}
async create(data: CreateScheduleRequest) {
return await this.post<CreateScheduleResponse>(
async create(data: DTO.ScheduleCreateRequest) {
return await this.post<DTO.ScheduleCreateResponse>(
`${this.baseUrl}${ScheduleApi.create}`,
data
);

View File

@@ -8,14 +8,13 @@ import { ScheduleNetwork } from "@/network/ScheduleNetwork";
import { ScheduleListData } from "@/data/response";
import { CustomCalendarCN } from "./CustomCalendarCN";
import { toast } from "sonner";
import { Converter } from "@/util/Converter";
import type { SchedulePopoverMode } from "@/const/schedule/SchedulePopoverMode";
import { SchedulerDTO as DTO, Type } from '@baekyangdan/core-utils';
interface CustomCalendarProps {
data?: any;
}
interface EventBarPosition extends ScheduleListData {
interface EventBarPosition extends DTO.ScheduleList {
positionStyle: React.CSSProperties;
trackIndex: number;
isOverflow?: boolean;
@@ -41,7 +40,7 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight });
const [maxVisibleEvents, setMaxVisibleEvents] = useState(3);
const [overflowTrackIndex, setOverflowTrackIndex] = useState(maxVisibleEvents + 1);
const [scheduleList, setScheduleList] = useState<Array<ScheduleListData>>([]);
const [scheduleList, setScheduleList] = useState<Array<DTO.ScheduleList>>([]);
const [barPositions, setBarPositions] = useState<Array<EventBarPosition>>([]);
const scheduleNetwork = new ScheduleNetwork();
const containerRef = useRef<HTMLDivElement>(null);
@@ -93,17 +92,17 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
const endDate = endOfWeek(monthEnd, { weekStartsOn: 0 });
const data = {
startDate: Converter.dateToUTC9(startDate),
endDate: Converter.dateToUTC9(endDate)
};
startDate: startDate,
endDate: endDate
} as DTO.ScheduleListRequest;
const result = await scheduleNetwork.getList(data);
if (result.data.success) {
if (result.data.data) {
if (result.success) {
if (result.data) {
if (isSameMonth(requestedMonth, month)) {
// setCurrentDataMonth(month);
setScheduleList(result.data.data!);
setScheduleList(result.data);
}
// setScheduleList(result.data.data);
}
@@ -221,7 +220,11 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
occupiedTrackList.set(dayKey, [...occupied, assignedTrack]);
}
}
scheduleListWithTrack.push({ ...schedule, trackIndex: assignedTrack });
scheduleListWithTrack.push({
...schedule,
startDate: schedule.startDate.toISOString(),
endDate: schedule.endDate.toISOString(),
trackIndex: assignedTrack });
} else {
for (const day of eventDays) {
const dayKey = format(day, DATE_FORMAT_KEY);
@@ -295,8 +298,8 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
overflowPositions.push({
id: `overflow-${dayKey}`,
name: `${count} more`,
startDate: Converter.dateToUTC9(new Date()),
endDate: Converter.dateToUTC9(new Date()),
startDate: new Date(),
endDate: new Date(),
style: '#9CA3AF',
trackIndex: overflowTrackIndex,
isOverflow: true,
@@ -339,6 +342,10 @@ export const CustomCalendar = ({ data }: CustomCalendarProps) => {
positions.push({
...schedule,
type: schedule.type as Type.Type,
status: schedule.status as Type.Status,
startDate: new Date(schedule.startDate),
endDate: new Date(schedule.endDate),
trackIndex: schedule.trackIndex,
id: schedule.id,
segmentId: `${schedule.id}-${renderStartKey}`,

View File

@@ -5,10 +5,8 @@ import { Popover, PopoverTrigger } from '@/components/ui/popover';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Textarea } from '@/components/ui/textarea';
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
import { type ColorPaletteType } from '@/const/ColorPalette';
import { ScheduleDay } from '@/const/schedule/ScheduleDay';
import type { ScheduleStatus } from '@/const/schedule/ScheduleStatus';
import { type ScheduleType } from '@/const/schedule/ScheduleType';
import type { ColorPaletteType } from '@/const/ColorPalette';
import { Type } from '@baekyangdan/core-utils';
import { CreateScheduleSchema } from '@/data/form/schedule/createSchedule.schema';
import { usePalette } from '@/hooks/use-palette';
import { useRecord } from '@/hooks/use-record';
@@ -27,17 +25,18 @@ import { TimePickPopover } from '../popover/TimePickPopover';
import { TypePickPopover } from '../popover/TypePickPopover';
import type { ScheduleCreateContentProps } from './ContentProps';
import { Converter } from '@/util/Converter';
import { SchedulerDTO as DTO } from '@baekyangdan/core-utils';
export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign, refetchList }: ScheduleCreateContentProps) => {
const [colorPopoverOpen, setColorPopoverOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { getPaletteByKey } = usePalette();
const { getCurrentTimeString, standardTimeToContinentalTime } = useTime();
const dayLabelList = useRecord(ScheduleDay).keys.map((key) => {
const dayLabelList = useRecord(Type.Day).keys.map((key) => {
return {
day: Number(key),
label: ScheduleDay[Number(key)]
} as { day: number, label: string };
day: key,
label: Type.Day[key as keyof typeof Type.Day]
} as { day: string, label: string };
});
const scheduleNetwork = new ScheduleNetwork();
@@ -66,7 +65,6 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign
startTime,
endTime,
type,
status,
style,
dayList,
// participantList
@@ -75,18 +73,29 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign
const reqCreate = async () => {
if (isLoading) return;
// const data = {
// name,
// startDate: Converter.dateToUTC9(startDate),
// endDate: Converter.dateToUTC9(endDate),
// content,
// startTime: standardTimeToContinentalTime(startTime),
// endTime: standardTimeToContinentalTime(endTime),
// type: type as Type.Type,
// status: status as Type.Status,
// style
// } as DTO.ScheduleCreateRequest;
const data = {
name,
startDate: Converter.dateToUTC9(startDate),
endDate: Converter.dateToUTC9(endDate),
content,
startDate: startDate,
endDate: endDate,
type: type as Type.Type,
style: style,
startTime: standardTimeToContinentalTime(startTime),
endTime: standardTimeToContinentalTime(endTime),
type: type as ScheduleType,
status: status as ScheduleStatus,
dayList,
style
};
content
} as DTO.ScheduleCreateRequest;
setIsLoading(true);
@@ -99,8 +108,8 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign
try {
const res = await createPromise;
if (!res.data.success) {
throw new Error(res.data.error);
if (!res.success) {
throw new Error(res.error);
}
toast.success('일정이 생성되었습니다');
@@ -123,7 +132,7 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign
setColorPopoverOpen(false);
}
const selectType = (type: ScheduleType) => {
const selectType = (type: Type.Type) => {
createScheduleForm.setValue('type', type);
}
@@ -259,7 +268,7 @@ export const ScheduleCreateContent = ({ date, setMode, popoverSide, popoverAlign
className="w-full h-full flex! flex-col! gap-4!"
>
<TypePickPopover
type={type as ScheduleType}
type={type as Type.Type}
setType={selectType}
popoverSide={popoverSide}
/>

View File

@@ -5,10 +5,11 @@ import { ArrowLeft, ChevronUp, Clock } from 'lucide-react';
import type { ScheduleDetailData } from '@/data/response';
import { cn } from '@/lib/utils';
import { ScrollArea } from '@/components/ui/scroll-area';
import { SchedulerDTO as DTO } from '@baekyangdan/core-utils'
export const ScheduleDetailContent = ({ setMode, popoverSide, popoverAlign, id }: ScheduleDetailContentProps) => {
const scheduleNetwork = new ScheduleNetwork();
const [data, setData] = useState<ScheduleDetailData>();
const [data, setData] = useState<DTO.ScheduleDetail>();
const [commentFold, setCommentFold] = useState(true);
useEffect(() => {
@@ -20,8 +21,8 @@ export const ScheduleDetailContent = ({ setMode, popoverSide, popoverAlign, id }
const reqDetail = async () => {
const result = await scheduleNetwork.getDetail(id);
if (result.data.success && result.data) {
setData(result.data.data!);
if (result.success) {
setData(result.data);
}
}

View File

@@ -13,10 +13,11 @@ import type { ScheduleType } from "@/const/schedule/ScheduleType";
import { ScheduleListData } from "@/data/response";
import { Converter } from "@/util/Converter";
import { ScheduleListTile } from "../tile/ScheduleListTile";
import { SchedulerDTO as DTO, Type } from "@baekyangdan/core-utils";
export const ScheduleListContent = ({ date, setMode, popoverAlign, popoverSide, open, setId }: ScheduleListContentProps) => {
const [isLoading, setIsLoading] = useState(false);
const [scheduleList, setScheduleList] = useState<Array<ScheduleListData>>([]);
const [scheduleList, setScheduleList] = useState<Array<DTO.ScheduleList>>([]);
const scheduleNetwork = new ScheduleNetwork();
const listScheduleForm = useForm<z.infer<typeof ListScheduleSchema>>({
@@ -52,16 +53,16 @@ export const ScheduleListContent = ({ date, setMode, popoverAlign, popoverSide,
const reqList = async () => {
const data = {
name,
date: Converter.dateToUTC9(searchDate),
status: status as ScheduleStatus | undefined,
styleList,
typeList: typeList as ScheduleType[] | undefined
};
date: searchDate,
status: status as Type.Status,
typeList: typeList as Array<Type.Type>,
styleList: styleList
} as DTO.ScheduleListRequest;
const result = await scheduleNetwork.getList(data);
if (result.data.success) {
setScheduleList(result.data.data!);
if (result.success) {
setScheduleList(result.data!);
}
}

View File

@@ -2,10 +2,10 @@ import type { SchedulePopoverMode } from "@/const/schedule/SchedulePopoverMode";
import { ScheduleTypeLabel } from "@/const/schedule/ScheduleType";
import { ScheduleListData } from "@/data/response";
import { Converter } from "@/util/Converter";
import { SchedulerDTO as DTO } from "@baekyangdan/core-utils";
interface ScheduleListTileProps {
setMode: (mode: SchedulePopoverMode) => void;
data: ScheduleListData;
data: DTO.ScheduleList;
onClick: (id: string) => void;
}
export const ScheduleListTile = ({ setMode, data, onClick }: ScheduleListTileProps) => {
@@ -38,7 +38,7 @@ export const ScheduleListTile = ({ setMode, data, onClick }: ScheduleListTilePro
</span>
</div>
<div className="flex-4 w-full flex flex-row text-xs font-light items-center text-indigo-200">
{formatter(data.startDate)} - {formatter(data.endDate)}
{formatter(data.startDate.toISOString())} - {formatter(data.endDate.toISOString())}
</div>
</div>

View File

@@ -11,7 +11,7 @@ import { PageRouting } from '@/const/PageRouting';
import * as z from 'zod';
import { Separator } from '@/components/ui/separator';
import { Validator } from '@/util/Validator';
import { LoginRequest } from '@/data/request/account/LoginRequest';
import { SchedulerDTO as DTO } from '@baekyangdan/core-utils';
import { AccountNetwork } from '@/network/AccountNetwork';
import { toast } from 'sonner';
import { useAuthStore } from '@/store/authStore';
@@ -56,7 +56,11 @@ export default function LoginPage() {
if (isLoading) return;
const type = Validator.isEmail(id) ? 'email' : 'accountId';
const data: LoginRequest = new LoginRequest(type, id, password);
const data = {
type,
id,
password
} as DTO.LoginRequest;
setIsLoading(true);
@@ -68,7 +72,7 @@ export default function LoginPage() {
loading: "로그인 중입니다.",
success: (res) => {
setIsLoading(false);
if (res.data.success) {
if (res.success) {
const data = {
accessToken: res.data.accessToken!
};
@@ -79,7 +83,7 @@ export default function LoginPage() {
moveToHomePage();
return "로그인 성공";
} else {
throw new Error(res.data.message);
throw new Error(res.message);
}
},
error: (err: Error) => {

View File

@@ -1,20 +1,20 @@
import { Card, CardContent, CardHeader, CardFooter } from '@/components/ui/card';
import { ResetPasswordSchema } from '@/data/form';
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card';
import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
import { Label } from '@/components/ui/label';
import { Stepper, StepperContent, StepperIndicator, StepperItem, StepperNav, StepperPanel, StepperSeparator, StepperTrigger } from '@/components/ui/stepper';
import { PageRouting } from '@/const/PageRouting';
import { ResetPasswordSchema } from '@/data/form';
import { AccountNetwork } from '@/network/AccountNetwork';
import { zodResolver } from '@hookform/resolvers/zod';
import React, { useState, useCallback } from 'react';
import { CircleCheckBigIcon, Eye, EyeOff, LoaderCircleIcon } from 'lucide-react';
import React, { useCallback, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { PageRouting } from '@/const/PageRouting';
import { Stepper, StepperContent, StepperIndicator, StepperItem, StepperNav, StepperPanel, StepperSeparator, StepperTrigger } from '@/components/ui/stepper';
import * as z from 'zod';
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
import { Validator } from '@/util/Validator';
import { Eye, EyeOff, LoaderCircleIcon, CircleCheckBigIcon } from 'lucide-react';
import { AccountNetwork } from '@/network/AccountNetwork';
import { Label } from '@/components/ui/label';
import { SchedulerDTO as DTO } from '@baekyangdan/core-utils';
const steps = [1, 2, 3, 4];
@@ -35,7 +35,7 @@ export default function ResetPasswordPage() {
passwordConfirm: ""
}
});
const { email, code, password, passwordConfirm } = resetPasswordForm.watch();
const moveToLoginPage = useCallback(() => {
@@ -63,11 +63,13 @@ export default function ResetPasswordPage() {
setIsLoading(true);
try {
const data = {
email
} as DTO.SendPasswordResetCodeRequest;
const response = await accountNetwork.sendResetPasswordCode({ email: email });
const resData = response.data;
if (!resData.success) {
const response = await accountNetwork.sendPasswordResetCode(data);
if (!response.success) {
resetPasswordForm.setError('email', {
message: '서버 오류로 코드 발송에 실패하였습니다. 잠시 후 다시 시도해주십시오.'
});
@@ -93,20 +95,19 @@ export default function ResetPasswordPage() {
}
const data = {
email: email,
code: code
}
email,
code
} as DTO.VerifyPasswordResetCodeRequest;
setIsLoading(true);
try {
const response = await accountNetwork.verifyResetPasswordCode(data);
const resData = response.data;
console.log(resData);
if (!resData.success || !resData.verified) {
const response = await accountNetwork.verifyPasswordResetCode(data);
if (!response.success || !response.data.verified) {
resetPasswordForm.setError('code', {
type: 'value',
message: resData.error
message: response.error
});
return;
}
@@ -131,17 +132,16 @@ export default function ResetPasswordPage() {
if (!passwordConfirmValid) return;
const data = {
email: email,
password: password
}
email,
password
} as DTO.ResetPasswordRequest;
setIsLoading(true);
try {
const response = await accountNetwork.resetPassword(data);
const resData = response.data;
if (!resData.success) {
if (!response.success) {
resetPasswordForm.setError('password', {
message: '서버 오류로 비밀번호 변경에 실패하였습니다. 잠시 후 다시 시도해주십시오.'
});
@@ -173,7 +173,7 @@ export default function ResetPasswordPage() {
}
return (
<Stepper
<Stepper
value={currentStep}
onValueChange={setCurrentStep}
className="w-full h-full flex flex-col justify-center items-center"
@@ -200,11 +200,11 @@ export default function ResetPasswordPage() {
</StepperIndicator>
</StepperTrigger>
{
steps.length > step
&& <StepperSeparator
className="transition-all duration-300 group-data-[state=completed]/step:bg-indigo-500"
/>
}
steps.length > step
&& <StepperSeparator
className="transition-all duration-300 group-data-[state=completed]/step:bg-indigo-500"
/>
}
</StepperItem>
))}
</StepperNav>
@@ -214,26 +214,26 @@ export default function ResetPasswordPage() {
<StepperContent value={1} key={1}>
<Field data-invalid={resetPasswordForm.formState.errors.email?.message ? true : false}>
<FieldLabel htmlFor="reseet-password-email"></FieldLabel>
<Controller
name="email"
control={resetPasswordForm.control}
render={({ field, fieldState }) => (
<>
<Input
{...field}
type="email"
id="reset-password-email"
aria-invalid={fieldState.invalid}
onKeyDown={handleEnterKeyDown}
/>
<FieldError className="font-[12px]" errors={[fieldState.error]} />
</>
)}
/>
</Field>
<Controller
name="email"
control={resetPasswordForm.control}
render={({ field, fieldState }) => (
<>
<Input
{...field}
type="email"
id="reset-password-email"
aria-invalid={fieldState.invalid}
onKeyDown={handleEnterKeyDown}
/>
<FieldError className="font-[12px]" errors={[fieldState.error]} />
</>
)}
/>
</Field>
</StepperContent>
<StepperContent value={2} key={2}>
<Controller
<Controller
name="code"
control={resetPasswordForm.control}
render={
@@ -241,7 +241,7 @@ export default function ResetPasswordPage() {
<div className="w-full flex flex-col justify-center gap-5">
<FieldLabel htmlFor="reset-password-code"> </FieldLabel>
<div className="flex flex-row justify-center items-center">
<InputOTP
<InputOTP
maxLength={8}
inputMode="text"
id="reset-password-code"
@@ -279,7 +279,7 @@ export default function ResetPasswordPage() {
<div className="relative w-full">
<Input
{...field}
type={ showPassword ? "text" : "password" }
type={showPassword ? "text" : "password"}
id="reset-password-password"
aria-invalid={fieldState.invalid}
onKeyDown={handleEnterKeyDown}
@@ -292,8 +292,8 @@ export default function ResetPasswordPage() {
>
{
showPassword
? <EyeOff size={14} />
: <Eye size={14} />
? <EyeOff size={14} />
: <Eye size={14} />
}
</button>
</div>
@@ -305,12 +305,12 @@ export default function ResetPasswordPage() {
<Controller
name="passwordConfirm"
control={resetPasswordForm.control}
render={({ field, fieldState}) => (
render={({ field, fieldState }) => (
<>
<div className="w-full relative">
<Input
{...field}
type={ showPasswordConfirm ? "text" : "password" }
type={showPasswordConfirm ? "text" : "password"}
id="reset-password-password-confirm"
className="pr-10"
aria-invalid={fieldState.invalid}
@@ -323,8 +323,8 @@ export default function ResetPasswordPage() {
>
{
showPasswordConfirm
? <EyeOff size={14} />
: <Eye size={14} />
? <EyeOff size={14} />
: <Eye size={14} />
}
</button>
</div>
@@ -348,7 +348,7 @@ export default function ResetPasswordPage() {
<StepperPanel className="w-full">
<StepperContent value={1}>
<div className="w-full flex flex-row items-center gap-5">
<Button
<Button
className="flex-8 bg-indigo-500 hover:bg-indigo-400"
type="button"
form="form-reset-password"
@@ -381,7 +381,7 @@ export default function ResetPasswordPage() {
</StepperContent>
<StepperContent value={3} key={3}>
<div className="w-full flex flex-row align-center gap-5">
<Button
<Button
className="flex-8 bg-indigo-500 hover:bg-indigo-400"
type="button"
form="form-reset-password"

View File

@@ -20,6 +20,7 @@ import { useNavigate } from 'react-router-dom';
import { PageRouting } from '@/const/PageRouting';
import { useIsMobile } from '@/hooks/use-mobile';
import { ScrollArea } from '@/components/ui/scroll-area';
import { SchedulerDTO as DTO } from '@baekyangdan/core-utils';
export default function SignUpPage() {
const [emailVerificationModalOpen, setEmailVerificationModalOpen] = useState<boolean>(false);
@@ -49,13 +50,22 @@ export default function SignUpPage() {
}, [navigate]);
const checkDuplication = async (type: 'email' | 'accountId', value: string) => {
const data: CheckDuplicationRequest = new CheckDuplicationRequest(type, value);
const data = {
type,
value
} as DTO.CheckDuplicationRequest;
return await accountNetwork.checkDuplication(data);
}
const signup = async () => {
const { email, accountId, name, nickname, password } = signUpForm.getValues();
const data: SignupRequest = new SignupRequest(accountId, email, name, nickname, password);
const data = {
email,
accountId,
name,
nickname,
password
} as DTO.SignupRequest;
const signupPromise = accountNetwork.signup(data);
@@ -64,7 +74,7 @@ export default function SignUpPage() {
{
loading: "회원가입 진행 중입니다.",
success: (res) => {
if (!res.data.success) return "회원가입에 실패하였습니다.\n잠시 후 다시 시도해주십시오.";
if (!res.success) return "회원가입에 실패하였습니다.\n잠시 후 다시 시도해주십시오.";
return <SuccessToast onClose={goToLogin} />
},
@@ -91,18 +101,36 @@ export default function SignUpPage() {
if (!value) return;
const isDuplicated = (await checkDuplication(type, value)).data.isDuplicated;
try {
if (isDuplicated) {
signUpForm.setError(type, { message: duplicatedMessage });
} else {
signUpForm.clearErrors(type);
if (type === 'email') {
setIsCheckedEmailDuplication(true);
setDuplicationCheckedEmail(value);
const result = await checkDuplication(type, value);
let isDuplicated = false;
if (result.success) {
isDuplicated = result.data.isDuplicated;
} else {
setIsCheckedAccountIdDuplication(true);
setDuplicationCheckedAccountId(value);
throw new Error(result.error);
}
if (isDuplicated) {
signUpForm.setError(type, { message: duplicatedMessage });
} else {
signUpForm.clearErrors(type);
if (type === 'email') {
setIsCheckedEmailDuplication(true);
setDuplicationCheckedEmail(value);
} else {
setIsCheckedAccountIdDuplication(true);
setDuplicationCheckedAccountId(value);
}
}
} catch (e) {
if (type === 'email') {
setIsCheckedEmailDuplication(false);
setDuplicationCheckedEmail('');
} else {
setIsCheckedAccountIdDuplication(false);
setDuplicationCheckedAccountId('');
}
}
}