- 자동 로그인 cookie 로 개선
This commit is contained in:
@@ -1 +1 @@
|
|||||||
VITE_API_URL=http://localhost:8088
|
VITE_API_URL=/dev-api
|
||||||
|
|||||||
28
certs/localhost+2-key.pem
Normal file
28
certs/localhost+2-key.pem
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1GqPn9O+FRM+a
|
||||||
|
hQvEAjgjI7HXdRdeRJsX3K2BqNGYBR8Lat4fMskY4Es7WYNCDl9d/fFQd+K/gpjj
|
||||||
|
sRnX0Bf3CYjVNlhTqdwoOFBPGUQ4i8fuSRM4rDvAq4enb+7RVWjE53MGgqQ0RdRQ
|
||||||
|
Hyx95pEWTz0FzpNlzOzEGEdv8zBwbwgZBU73F4aCDNY0FLaaKepr1/NqdUA8xZYo
|
||||||
|
sAbJvIkWJ/QS2F2/WwQZYQ3TLWtg4/2uDpGWbpQUwRnWZ7ma+Gcz/hJwQhoml4q4
|
||||||
|
bHXrIAsCz/NFNYs7K5wCmIdTjLw2PcETJSEQMbGSk7CMIuiUd0ShVDfpBROIAlmF
|
||||||
|
XAheZ/lNAgMBAAECggEAT1H7t/xva89XnjXnkVHnhHx9yABg28jwpOLim4d1RT/4
|
||||||
|
+Oc1ojR8H4kdakEqXCQvYNt4deYMShTJIfDPgNaDqI9kfv3ucbZT1snTYtGOL7YJ
|
||||||
|
OzSGVqwY/6ohIBTGZKkj2hoFJzTQ9pQfCXid5Aa4RS0vbPutU0kN6lU39LBu5s79
|
||||||
|
8MGGu70Qcd63BRpyOxKbbWCbZ0S/7JRShng0GA8ILBvMCZdzZ7RZktpa+bA7Q6i7
|
||||||
|
PM5zxxmxygmFQXOAizTB2KoeLEu/qb44kypK6aw9nMKEZebqzuF5bOFMlCR67Cfx
|
||||||
|
QblFW0JQckef7DCc4ThPEnAXwEPSlsv2P//dbX1TgQKBgQDctIA/GZmzSXVtsUws
|
||||||
|
aPPeAvKMWGFSGsq/9rcy2G7KBTHBQW5/763T6N7HKVpZwRaabiSxgrZekd6d9oTP
|
||||||
|
XWpmnFQZgtLRtXLPXilCK8Udoi6HejGuXWvviFdfPUqhPh3tw6uCaFO7qT4pUiwO
|
||||||
|
hTq4W5Cm+xy/q12m7cEDfoPeMwKBgQDSEOdQY5f5C6YrZvG2zrTZUvS8VW4NFhkY
|
||||||
|
vSZBGqiACYGKq/7erMoAdtLrcYBnEMiLufn+tSInflw2I+ALEo4r2lcBAQncXuWj
|
||||||
|
gnpM+DpTwRfZmGYs3jXA5eVRRzG0FBFLFyiBE62f0tvJjALB7V7vfpCswJGvPIyb
|
||||||
|
p2P16jZKfwKBgQCn4VkoJlYGzZrYTKPvqAnQV5ed7+BfbufIq2dg8scbPmZBZX8j
|
||||||
|
K/KinaFQB4Glgj2qTJv2tsH4H6chqxINFjbIRKOoIB4yzH2/hRWHMvomd2ZDQUyn
|
||||||
|
IILo2mHznRC2pCRp5owAj1EaDzusfMfsZ6Vp9KSMj7inhzeesX0/Ji4yhwKBgHgh
|
||||||
|
sJc5jYSgU9RIV/0qcyRBm7JEzN3xAEM0kLb0rt4iEZIjUGs5t3/SdEavLzZB096M
|
||||||
|
adpu7exWCBfyJkNOxj1v7Qem92OuZXc/u/9eicSyDZij3fLU1TrOfnkf1N3eCBHA
|
||||||
|
WaqPfWCELqsxRbZvsDYYVFZm/imP3/14GeNdoNSzAoGALFD3YdZ5bIETcD8hwXd7
|
||||||
|
P9HXPQZag919fFe0dhPDauC5dgvgfLF2d88q0MPCqMK6ngTMVzrMh4X/u0XH1N3v
|
||||||
|
LiQEBp4P0EX1z5DEj8eLajV0JgOyt1zzymZ3Jk7DDyfcYl+bVetB2R9Gd3MuV98t
|
||||||
|
Sz72wO/jh1/w+SN07F9VS0w=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
26
certs/localhost+2.pem
Normal file
26
certs/localhost+2.pem
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIETjCCAragAwIBAgIQJ0x1ttig1msjzZOJWJwnITANBgkqhkiG9w0BAQsFADB7
|
||||||
|
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKDAmBgNVBAsMH+uwse2W
|
||||||
|
peuLqFxiYWVreWFuZ2RhbkDrsLHtlqXri6gxLzAtBgNVBAMMJm1rY2VydCDrsLHt
|
||||||
|
lqXri6hcYmFla3lhbmdkYW5A67Cx7Zal64uoMB4XDTI1MTIwNzEwMDIyNVoXDTI4
|
||||||
|
MDMwNzEwMDIyNVowUzEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRp
|
||||||
|
ZmljYXRlMSgwJgYDVQQLDB/rsLHtlqXri6hcYmFla3lhbmdkYW5A67Cx7Zal64uo
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtRqj5/TvhUTPmoULxAI4
|
||||||
|
IyOx13UXXkSbF9ytgajRmAUfC2reHzLJGOBLO1mDQg5fXf3xUHfiv4KY47EZ19AX
|
||||||
|
9wmI1TZYU6ncKDhQTxlEOIvH7kkTOKw7wKuHp2/u0VVoxOdzBoKkNEXUUB8sfeaR
|
||||||
|
Fk89Bc6TZczsxBhHb/MwcG8IGQVO9xeGggzWNBS2minqa9fzanVAPMWWKLAGybyJ
|
||||||
|
Fif0Ethdv1sEGWEN0y1rYOP9rg6Rlm6UFMEZ1me5mvhnM/4ScEIaJpeKuGx16yAL
|
||||||
|
As/zRTWLOyucApiHU4y8Nj3BEyUhEDGxkpOwjCLolHdEoVQ36QUTiAJZhVwIXmf5
|
||||||
|
TQIDAQABo3YwdDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||||
|
HwYDVR0jBBgwFoAU9/lqZ9lo2f1oLg+JnBYubfpE4OEwLAYDVR0RBCUwI4IJbG9j
|
||||||
|
YWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IB
|
||||||
|
gQBNsZYSSGE6m3ve8bPGISSdlSU/pi1GVqOC4xxVD8JUeGNZeYAv8AJQ6w/496wK
|
||||||
|
KTFL6PDOavHW37mWBEgz+fZe2AjOZK/hz/eOcKHTFhVRZo1snt5VuLk4PtAGmgn8
|
||||||
|
xyyQUz/2wwlTqb0AgLrt1hLTnbSIWvBnFl3VdCnH0E2xsIPZUMFzcjVHTERJWvAS
|
||||||
|
IanurnpeO/W3uNduu7UmGk03GDzTG8dXwVsSSE/HoXxscXSIP9qMvKOPeQvucd+X
|
||||||
|
XkaYUkbjDhwoKqDB0rDmvbPkFcsAGuq8qpbPPavhoXgtdqO30lZfbTPWPq1qz99S
|
||||||
|
nW9ihMfmwarw5s/LCXiQO70nMbcZMZ6UAqEhX4UUfGD0j5jHe3fPOyT4v0lLfmxp
|
||||||
|
P4eoSiWoIfp/f9ZBn9zca5km9iGNT+n1Jrt6fOTIycPVu2gE7S4qXcHhVHWondle
|
||||||
|
bMFLGbjZ75Qwc9HdnoY7Do8Vj+CPvSfhAAPehPf8D1GVlazmiuHql4fny1qbGw//
|
||||||
|
YEQ=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
13
src/App.tsx
13
src/App.tsx
@@ -10,16 +10,20 @@ import { HomePage } from './ui/page/home/HomePage';
|
|||||||
import type { AuthData } from './data/AuthData';
|
import type { AuthData } from './data/AuthData';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { ScheduleMainPage } from './ui/page/schedule/ScheduleMainPage';
|
import { ScheduleMainPage } from './ui/page/schedule/ScheduleMainPage';
|
||||||
|
import { BaseNetwork } from './network/BaseNetwork';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { authData, login } = useAuthStore();
|
const { authData, login } = useAuthStore();
|
||||||
|
const baseNetwork = new BaseNetwork();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const autoLogin = localStorage.getItem('autoLogin') === 'true';
|
const autoLogin = localStorage.getItem('autoLogin') === 'true';
|
||||||
if (autoLogin) {
|
if (autoLogin) {
|
||||||
const stored = localStorage.getItem('auth-storage');
|
try {
|
||||||
if (stored) {
|
(async () => {
|
||||||
const storedAuthData = JSON.parse(stored).state as AuthData;
|
await baseNetwork.refreshToken();
|
||||||
login(storedAuthData);
|
})();
|
||||||
|
} catch (err) {
|
||||||
|
localStorage.setItem('autoLogin', 'false');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -35,6 +39,7 @@ function App() {
|
|||||||
<Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} />
|
<Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} />
|
||||||
<Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} />
|
<Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} />
|
||||||
<Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" />
|
<Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" />
|
||||||
|
<Route element={<div />} path={"/"} /> {/* 자동로그인용 대기 화면 */}
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<Route element={<Navigate to={PageRouting["HOME"].path} />} path="*" />
|
<Route element={<Navigate to={PageRouting["HOME"].path} />} path="*" />
|
||||||
|
|||||||
1
src/const/schedule/ScheduleStatus.ts
Normal file
1
src/const/schedule/ScheduleStatus.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type ScheduleStatus = 'yet' | 'completed';
|
||||||
1
src/const/schedule/ScheduleType.ts
Normal file
1
src/const/schedule/ScheduleType.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type ScheduleType = 'once' | 'daily' | 'weekly' | 'aweekly' | 'monthly' | 'annual';
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
export type AuthData = {
|
export type AuthData = {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
refreshToken: string;
|
|
||||||
}
|
}
|
||||||
22
src/data/form/createSchedule.schema.ts
Normal file
22
src/data/form/createSchedule.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
|
export const CreateScheduleSchema = z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
, startDate: z
|
||||||
|
.date()
|
||||||
|
, endDate: z
|
||||||
|
.date()
|
||||||
|
, status: z
|
||||||
|
.string()
|
||||||
|
, content: z
|
||||||
|
.string()
|
||||||
|
, type: z
|
||||||
|
.string()
|
||||||
|
, style: z
|
||||||
|
.string()
|
||||||
|
, startTime: z
|
||||||
|
.string()
|
||||||
|
, endTime: z
|
||||||
|
.string()
|
||||||
|
});
|
||||||
24
src/data/form/updateSchedule.schema.ts
Normal file
24
src/data/form/updateSchedule.schema.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import * as z from 'zod';
|
||||||
|
|
||||||
|
export const UpdateScheduleSchema = z.object({
|
||||||
|
id: z
|
||||||
|
.string()
|
||||||
|
, name: z
|
||||||
|
.string()
|
||||||
|
, startDate: z
|
||||||
|
.date()
|
||||||
|
, endDate: z
|
||||||
|
.date()
|
||||||
|
, status: z
|
||||||
|
.string()
|
||||||
|
.default("yet")
|
||||||
|
, type: z
|
||||||
|
.string()
|
||||||
|
.default("once")
|
||||||
|
, style: z
|
||||||
|
.string()
|
||||||
|
, startTime: z
|
||||||
|
.string()
|
||||||
|
, endTime: z
|
||||||
|
.string()
|
||||||
|
});
|
||||||
@@ -5,4 +5,9 @@ export * from './account/SignupRequest';
|
|||||||
export * from './account/LoginRequest';
|
export * from './account/LoginRequest';
|
||||||
export * from './account/SendResetPasswordCodeRequest';
|
export * from './account/SendResetPasswordCodeRequest';
|
||||||
export * from './account/VerifyResetPasswordCodeRequest';
|
export * from './account/VerifyResetPasswordCodeRequest';
|
||||||
export * from './account/ResetPasswordRequest';
|
export * from './account/ResetPasswordRequest';
|
||||||
|
|
||||||
|
export * from './schedule/CreateScheduleRequest';
|
||||||
|
export * from './schedule/DeleteScheduleRequest';
|
||||||
|
export * from './schedule/ScheduleListRequest';
|
||||||
|
export * from './schedule/UpdateScheduleRequest';
|
||||||
39
src/data/request/schedule/CreateScheduleRequest.ts
Normal file
39
src/data/request/schedule/CreateScheduleRequest.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { ScheduleStatus } from "@/const/schedule/ScheduleStatus";
|
||||||
|
import type { ScheduleType } from "@/const/schedule/ScheduleType";
|
||||||
|
|
||||||
|
export class CreateScheduleRequest {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
status: ScheduleStatus;
|
||||||
|
type: ScheduleType;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
style: string;
|
||||||
|
participantList: string[];
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
name: string,
|
||||||
|
content: string,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
status: ScheduleStatus,
|
||||||
|
type: ScheduleType,
|
||||||
|
startTime: string,
|
||||||
|
endTime: string,
|
||||||
|
style: string,
|
||||||
|
participantList: string[]
|
||||||
|
) {
|
||||||
|
this.name = name;
|
||||||
|
this.content = content;
|
||||||
|
this.startDate = startDate;
|
||||||
|
this.endDate = endDate;
|
||||||
|
this.status = status;
|
||||||
|
this.type = type;
|
||||||
|
this.startTime = startTime;
|
||||||
|
this.endTime = endTime;
|
||||||
|
this.style = style;
|
||||||
|
this.participantList = participantList;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/data/request/schedule/DeleteScheduleRequest.ts
Normal file
7
src/data/request/schedule/DeleteScheduleRequest.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export class DeleteScheduleRequest {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
constructor(id: string) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/data/request/schedule/ScheduleListRequest.ts
Normal file
13
src/data/request/schedule/ScheduleListRequest.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { ScheduleStatus } from "@/const/schedule/ScheduleStatus";
|
||||||
|
import type { ScheduleType } from "@/const/schedule/ScheduleType";
|
||||||
|
|
||||||
|
export class ScheduleListRequest {
|
||||||
|
filterAccountIdList?: string[];
|
||||||
|
filterStartDate?: Date;
|
||||||
|
filterEndDate?: Date;
|
||||||
|
filterDate?: Date;
|
||||||
|
filterTypeList?: ScheduleType[];
|
||||||
|
filterStyleList?: string[];
|
||||||
|
filterStatusList?: ScheduleStatus[];
|
||||||
|
filterName?: string;
|
||||||
|
}
|
||||||
42
src/data/request/schedule/UpdateScheduleRequest.ts
Normal file
42
src/data/request/schedule/UpdateScheduleRequest.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type { ScheduleStatus } from "@/const/schedule/ScheduleStatus";
|
||||||
|
import type { ScheduleType } from "@/const/schedule/ScheduleType";
|
||||||
|
|
||||||
|
export class UpdateScheduleRequest {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
status: 'yet' | 'completed';
|
||||||
|
type: 'once' | 'daily' | 'weekly' | 'aweekly' | 'monthly' | 'annual';
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
style: string;
|
||||||
|
participantList: string[];
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
content: string,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
status: ScheduleStatus,
|
||||||
|
type: ScheduleType,
|
||||||
|
startTime: string,
|
||||||
|
endTime: string,
|
||||||
|
style: string,
|
||||||
|
participantList: string[]
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.content = content;
|
||||||
|
this.startDate = startDate;
|
||||||
|
this.endDate = endDate;
|
||||||
|
this.status = status;
|
||||||
|
this.type = type;
|
||||||
|
this.startTime = startTime;
|
||||||
|
this.endTime = endTime;
|
||||||
|
this.style = style;
|
||||||
|
this.participantList = participantList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,5 +2,4 @@ import { BaseResponse } from "../BaseResponse";
|
|||||||
|
|
||||||
export class LoginResponse extends BaseResponse {
|
export class LoginResponse extends BaseResponse {
|
||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
refreshToken?: string;
|
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@ import { BaseResponse } from "../BaseResponse";
|
|||||||
|
|
||||||
export class RefreshAccessTokenResponse extends BaseResponse {
|
export class RefreshAccessTokenResponse extends BaseResponse {
|
||||||
accessToken!: string;
|
accessToken!: string;
|
||||||
refreshToken!: string;
|
refreshToken?: string;
|
||||||
}
|
}
|
||||||
@@ -5,4 +5,7 @@ export * from './account/SignupResponse';
|
|||||||
export * from './account/LoginResponse';
|
export * from './account/LoginResponse';
|
||||||
export * from './account/SendResetPasswordCodeResponse';
|
export * from './account/SendResetPasswordCodeResponse';
|
||||||
export * from './account/VerifyResetPasswordCodeResponse';
|
export * from './account/VerifyResetPasswordCodeResponse';
|
||||||
export * from './account/ResetPasswordResponse';
|
export * from './account/ResetPasswordResponse';
|
||||||
|
|
||||||
|
export * from './schedule/CreateScheduleResponse';
|
||||||
|
export * from './schedule/UpdateScheduleResponse';
|
||||||
|
|||||||
3
src/data/response/schedule/CreateScheduleResponse.ts
Normal file
3
src/data/response/schedule/CreateScheduleResponse.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { BaseResponse } from "../BaseResponse";
|
||||||
|
|
||||||
|
export class CreateScheduleResponse extends BaseResponse {}
|
||||||
3
src/data/response/schedule/UpdateScheduleResponse.ts
Normal file
3
src/data/response/schedule/UpdateScheduleResponse.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { BaseResponse } from "../BaseResponse";
|
||||||
|
|
||||||
|
export class UpdateScheduleResponse extends BaseResponse {}
|
||||||
@@ -18,7 +18,7 @@ export class BaseNetwork {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.instance = axios.create({
|
this.instance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_URL || "http://localhost:3000",
|
baseURL: import.meta.env.VITE_API_URL || "https://localhost:3000",
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -40,7 +40,24 @@ export class BaseNetwork {
|
|||||||
if (reqConfig.authPass) {
|
if (reqConfig.authPass) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
const accessToken = localStorage.getItem("accessToken");
|
|
||||||
|
let accessToken = useAuthStore.getState().authData?.accessToken;
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
const authStorage = localStorage.getItem('auth-storage');
|
||||||
|
if (authStorage) {
|
||||||
|
try {
|
||||||
|
const parsedState = JSON.parse(authStorage).state;
|
||||||
|
accessToken = parsedState.authData?.accessToken || null;
|
||||||
|
|
||||||
|
if (accessToken) {
|
||||||
|
useAuthStore.getState().login({ accessToken });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse auth-storage for Access Token:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
config.headers.Authorization = `Bearer ${accessToken}`;
|
config.headers.Authorization = `Bearer ${accessToken}`;
|
||||||
}
|
}
|
||||||
@@ -77,9 +94,8 @@ export class BaseNetwork {
|
|||||||
private async handleRefreshToken(originalRequest: AxiosRequestConfig) {
|
private async handleRefreshToken(originalRequest: AxiosRequestConfig) {
|
||||||
|
|
||||||
const authData = useAuthStore.getState().authData;
|
const authData = useAuthStore.getState().authData;
|
||||||
const refreshToken = authData?.refreshToken;
|
|
||||||
|
|
||||||
if (!authData || !refreshToken) {
|
if (!authData) {
|
||||||
useAuthStore.getState().logout();
|
useAuthStore.getState().logout();
|
||||||
return Promise.reject("no refresh token");
|
return Promise.reject("no refresh token");
|
||||||
}
|
}
|
||||||
@@ -141,7 +157,7 @@ export class BaseNetwork {
|
|||||||
|
|
||||||
const authData: AuthData = JSON.parse(storedAuth).state;
|
const authData: AuthData = JSON.parse(storedAuth).state;
|
||||||
|
|
||||||
if (!authData || !authData.refreshToken) {
|
if (!authData) {
|
||||||
localStorage.setItem('autoLogin', 'false');
|
localStorage.setItem('autoLogin', 'false');
|
||||||
throw new Error;
|
throw new Error;
|
||||||
}
|
}
|
||||||
@@ -149,20 +165,16 @@ export class BaseNetwork {
|
|||||||
const result = await this.get<RefreshAccessTokenResponse>(
|
const result = await this.get<RefreshAccessTokenResponse>(
|
||||||
'/account/refresh-access-token',
|
'/account/refresh-access-token',
|
||||||
{
|
{
|
||||||
headers: {
|
withCredentials: true
|
||||||
Authorization: `Bearer ${authData.refreshToken}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!result.data.success) throw new Error;
|
if (!result.data.success) throw new Error;
|
||||||
|
|
||||||
const newAccessToken = result.data.accessToken;
|
const newAccessToken = result.data.accessToken;
|
||||||
const newRefreshToken = result.data.refreshToken;
|
|
||||||
|
|
||||||
useAuthStore.getState().login({
|
useAuthStore.getState().login({
|
||||||
accessToken: newAccessToken,
|
accessToken: newAccessToken
|
||||||
refreshToken: newRefreshToken
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
43
src/network/ScheduleNetwork.ts
Normal file
43
src/network/ScheduleNetwork.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import type { CreateScheduleRequest } from "@/data/request/schedule/CreateScheduleRequest";
|
||||||
|
import { BaseNetwork } from "./BaseNetwork"
|
||||||
|
import type { UpdateScheduleRequest } from "@/data/request/schedule/UpdateScheduleRequest";
|
||||||
|
import type { DeleteScheduleRequest } from "@/data/request/schedule/DeleteScheduleRequest";
|
||||||
|
import type { ScheduleListRequest } from "@/data/request/schedule/ScheduleListRequest";
|
||||||
|
|
||||||
|
export class ScheduleNetwork extends BaseNetwork {
|
||||||
|
private baseUrl = "/schedule";
|
||||||
|
|
||||||
|
async getList(data: ScheduleListRequest) {
|
||||||
|
return await this.post(
|
||||||
|
this.baseUrl,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDetail(id: string) {
|
||||||
|
return await this.get(
|
||||||
|
`${this.baseUrl}/${id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: CreateScheduleRequest) {
|
||||||
|
return await this.post(
|
||||||
|
`${this.baseUrl}/create`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(data: UpdateScheduleRequest) {
|
||||||
|
return await this.post(
|
||||||
|
`${this.baseUrl}/update`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(data: DeleteScheduleRequest) {
|
||||||
|
return await this.post(
|
||||||
|
`${this.baseUrl}/delete`,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,7 @@ export const ColorPickPopover = ({ setColor }: ColorPickPopoverProps) => {
|
|||||||
const [seeMore, setSeeMore] = useState(false);
|
const [seeMore, setSeeMore] = useState(false);
|
||||||
const {
|
const {
|
||||||
getMainPaletteList,
|
getMainPaletteList,
|
||||||
getExtraPaletteList,
|
getExtraPaletteList
|
||||||
getCustomColor
|
|
||||||
} = usePalette();
|
} = usePalette();
|
||||||
const mainPaletteList = getMainPaletteList();
|
const mainPaletteList = getMainPaletteList();
|
||||||
const extraPaletteList = getExtraPaletteList();
|
const extraPaletteList = getExtraPaletteList();
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import { usePalette } from '@/hooks/use-palette';
|
|||||||
import { type ColorPaletteType } from '@/const/ColorPalette';
|
import { type ColorPaletteType } from '@/const/ColorPalette';
|
||||||
import { ColorPickPopover } from './ColorPickPopover';
|
import { ColorPickPopover } from './ColorPickPopover';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
|
import * as z from 'zod';
|
||||||
|
import { CreateScheduleSchema } from '@/data/form/createSchedule.schema';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { Field, FieldError } from '@/components/ui/field';
|
||||||
|
import { ArrowLeft, PenSquare, X } from 'lucide-react';
|
||||||
|
|
||||||
interface ScheduleSheetProps {
|
interface ScheduleSheetProps {
|
||||||
date: Date | undefined;
|
date: Date | undefined;
|
||||||
@@ -16,22 +22,94 @@ interface ScheduleSheetProps {
|
|||||||
|
|
||||||
export const SchedulePopover = ({ date, popoverSide, popoverAlign }: ScheduleSheetProps) => {
|
export const SchedulePopover = ({ date, popoverSide, popoverAlign }: ScheduleSheetProps) => {
|
||||||
const {
|
const {
|
||||||
ColorPaletteType,
|
|
||||||
getPaletteNameList,
|
|
||||||
getMainPaletteList,
|
|
||||||
getAllPaletteList,
|
|
||||||
getCustomColor,
|
|
||||||
getPaletteByKey,
|
getPaletteByKey,
|
||||||
getStyle
|
|
||||||
} = usePalette();
|
} = usePalette();
|
||||||
const defaultColor = getPaletteByKey('Black');
|
const defaultColor = getPaletteByKey('Black');
|
||||||
const [scheduleColor, setScheduleColor] = useState(defaultColor);
|
const [scheduleColor, setScheduleColor] = useState(defaultColor);
|
||||||
const [colorPopoverOpen, setColorPopoverOpen] = useState(false);
|
const [colorPopoverOpen, setColorPopoverOpen] = useState(false);
|
||||||
|
const [mode, setMode] = useState<'list' | 'detail' | 'create' | 'edit'>('list');
|
||||||
const selectColor = (color: ColorPaletteType) => {
|
const selectColor = (color: ColorPaletteType) => {
|
||||||
setScheduleColor(color);
|
setScheduleColor(color);
|
||||||
setColorPopoverOpen(false);
|
setColorPopoverOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createScheduleForm = useForm<z.infer<typeof CreateScheduleSchema>>({
|
||||||
|
resolver: zodResolver(CreateScheduleSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
startDate: date,
|
||||||
|
endDate: date,
|
||||||
|
content: "",
|
||||||
|
startTime: "",
|
||||||
|
endTime: "",
|
||||||
|
type: "once",
|
||||||
|
status: "yet",
|
||||||
|
style: defaultColor.style,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Content = () => {
|
||||||
|
switch(mode) {
|
||||||
|
case 'list':
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PenSquare onClick={() => setMode('create')}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case 'create':
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex flex-col justify-start items-start gap-2.5">
|
||||||
|
<div className="w-full flex flex-row justify-center items-center gap-5">
|
||||||
|
<Popover open={colorPopoverOpen} onOpenChange={setColorPopoverOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'rounded-full w-5 h-5 border-2 border-gray-300',
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
backgroundColor: `${scheduleColor.style !== 'transparent' && scheduleColor.style}`,
|
||||||
|
background: `${scheduleColor.style === 'transparent' && 'linear-gradient(135deg, black 50%, white 50%)' }`
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<ColorPickPopover
|
||||||
|
setColor={selectColor}
|
||||||
|
/>
|
||||||
|
</Popover>
|
||||||
|
<Controller
|
||||||
|
name="name"
|
||||||
|
control={createScheduleForm.control}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<Field data-invalid={fieldState.invalid}>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="form-create-schedule-name"
|
||||||
|
placeholder="제목"
|
||||||
|
className="font-bold border-t-0 border-r-0 border-l-0 p-0 border-b-2 rounded-none shadow-none border-indigo-300 focus-visible:ring-0 focus-visible:border-b-indigo-500"
|
||||||
|
style={{
|
||||||
|
fontSize: '20px'
|
||||||
|
}}
|
||||||
|
tabIndex={1}
|
||||||
|
arai-invalid={fieldState.invalid}
|
||||||
|
/>
|
||||||
|
<FieldError errors={[fieldState.error]} />
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex flex-row self-end justify-self-end items-center justify-center cursor-default"
|
||||||
|
onClick={() => setMode('list')}
|
||||||
|
>
|
||||||
|
<X />
|
||||||
|
<span>닫기</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
default: return (<></>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
className="rounded-xl xl:w-[calc(100vw/4)] xl:max-w-[480px] min-w-[320px]"
|
className="rounded-xl xl:w-[calc(100vw/4)] xl:max-w-[480px] min-w-[320px]"
|
||||||
@@ -44,31 +122,7 @@ export const SchedulePopover = ({ date, popoverSide, popoverAlign }: ScheduleShe
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex flex-row justify-center items-center gap-4">
|
{<Content />}
|
||||||
<Popover open={colorPopoverOpen} onOpenChange={setColorPopoverOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'rounded-full w-5 h-5 border-2 border-gray-300',
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
backgroundColor: `${scheduleColor.style !== 'transparent' && scheduleColor.style}`,
|
|
||||||
background: `${scheduleColor.style === 'transparent' && 'linear-gradient(135deg, black 50%, white 50%)' }`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<ColorPickPopover
|
|
||||||
setColor={selectColor}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
<Input
|
|
||||||
placeholder="제목"
|
|
||||||
className="font-bold border-t-0 border-r-0 border-l-0 p-0 border-b-2 rounded-none shadow-none border-indigo-300 focus-visible:ring-0 focus-visible:border-b-indigo-500"
|
|
||||||
style={{
|
|
||||||
fontSize: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,8 +70,7 @@ export default function LoginPage() {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
const data = {
|
const data = {
|
||||||
accessToken: res.data.accessToken!,
|
accessToken: res.data.accessToken!
|
||||||
refreshToken: res.data.refreshToken!
|
|
||||||
};
|
};
|
||||||
login({...data});
|
login({...data});
|
||||||
if (autoLogin) {
|
if (autoLogin) {
|
||||||
|
|||||||
@@ -2,12 +2,37 @@ import { defineConfig } from 'vite'
|
|||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
const certPath = path.resolve(__dirname, 'certs');
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 5185
|
port: 5185,
|
||||||
|
https: {
|
||||||
|
key: fs.readFileSync(path.join(certPath, 'localhost+2-key.pem')),
|
||||||
|
cert: fs.readFileSync(path.join(certPath, 'localhost+2.pem'))
|
||||||
|
},
|
||||||
|
proxy: {
|
||||||
|
'/local-api': {
|
||||||
|
target: 'https://localhost:3000',
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/local-api/, ''),
|
||||||
|
},
|
||||||
|
'/dev-api': {
|
||||||
|
target: 'https://localhost:8088',
|
||||||
|
secure: false,
|
||||||
|
ws: true,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/dev-api/, ''),
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
tailwindcss()
|
tailwindcss()
|
||||||
|
|||||||
Reference in New Issue
Block a user