diff --git a/src/preload/src/index.ts b/src/preload/src/index.ts index 007ec6d..8ffc4a8 100644 --- a/src/preload/src/index.ts +++ b/src/preload/src/index.ts @@ -21,6 +21,7 @@ type VersionInfo = { // 플랫폼 정보 (예시) type PlatformInfo = { + // eslint-disable-next-line no-undef os: NodeJS.Platform; arch: string; }; @@ -73,6 +74,15 @@ interface ElectronAPI { // 시스템 테마 조회 getSystemTheme: () => Promise; + + // 알림 API + notification: { + show: ( + title: string, + body: string, + ) => Promise<{ success: boolean; error?: string }>; + requestPermission: () => Promise<{ success: boolean; supported: boolean }>; + }; } // Expose version number to renderer @@ -170,5 +180,17 @@ const electronAPI: ElectronAPI = { ipcRenderer.invoke('theme:getSystemTheme') as ReturnType< ElectronAPI['getSystemTheme'] >, + + // 알림 API + notification: { + show: (title: string, body: string) => + ipcRenderer.invoke('notification:show', title, body) as ReturnType< + ElectronAPI['notification']['show'] + >, + requestPermission: () => + ipcRenderer.invoke('notification:requestPermission') as ReturnType< + ElectronAPI['notification']['requestPermission'] + >, + }, }; contextBridge.exposeInMainWorld('electronAPI', electronAPI); diff --git a/src/renderer/src/api/api.ts b/src/renderer/src/api/api.ts index 9bc9c9d..84d4a4b 100644 --- a/src/renderer/src/api/api.ts +++ b/src/renderer/src/api/api.ts @@ -1,4 +1,16 @@ import axios, { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'; +import { Ref } from 'react'; + +interface RefreshResponse { + timestamp: string; + success: boolean; + data: { + accessToken: string; + refreshToken: string; + }; + code: string; + message: string | null; +} const api: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_BASE_URL as string, @@ -28,28 +40,37 @@ api.interceptors.response.use( _retry?: boolean; }; // 무한 요청 방지 - if (error.response?.status === 401 && !originalRequest._retry) { + /* 에러가 401,403일 때 토큰 갱신 */ + if ( + (error.response?.status === 401 || error.response?.status === 403) && + !originalRequest._retry + ) { originalRequest._retry = true; try { const refreshToken = localStorage.getItem('refreshToken'); - const { data: newToken } = await axios.post<{ - accessToken: string; - refreshToken: string; - }>( + const { data: newToken } = await axios.post( `${import.meta.env.VITE_BASE_URL}/auth/refresh`, { refreshToken }, { withCredentials: true }, ); - localStorage.setItem('accessToken', newToken.accessToken); - localStorage.setItem('refreshToken', newToken.refreshToken); + /* success가 false이거나 응답으로 온 데이터가 비었을 때 */ + if (!newToken.success || !newToken.data) { + throw new Error('Refresh token expired'); + } + + /* 새로 발급 받은 토큰 저장 */ + const newAccessToken = newToken.data.accessToken; + const newRefreshToken = newToken.data.refreshToken; + + localStorage.setItem('accessToken', newAccessToken); + localStorage.setItem('refreshToken', newRefreshToken); api.defaults.headers.common['Authorization'] = - `Bearer ${newToken.accessToken}`; + `Bearer ${newAccessToken}`; if (originalRequest.headers) { - originalRequest.headers['Authorization'] = - `Bearer ${newToken.accessToken}`; + originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`; } return api(originalRequest); diff --git a/src/renderer/src/api/dashboard/useAverageScoreQuery.ts b/src/renderer/src/api/dashboard/useAverageScoreQuery.ts new file mode 100644 index 0000000..d86f643 --- /dev/null +++ b/src/renderer/src/api/dashboard/useAverageScoreQuery.ts @@ -0,0 +1,33 @@ +import { useQuery } from '@tanstack/react-query'; +import api from '../api'; +import { AverageScoreResponse } from '../../types/dashboard/averageScore'; + +/** + * 평균 자세 점수 조회 API + * GET /dashboard/average-score + */ +const getAverageScore = async (): Promise => { + const response = await api.get( + '/dashboard/average-score', + ); + const result = response.data; + + if (!result.success) { + throw new Error(result.message || '평균 점수 조회 실패'); + } + + return result; +}; + +/** + * 평균 자세 점수 조회 query 훅 + * @example + * const { data, isLoading, error } = useAverageScoreQuery(); + * const score = data?.data.score; + */ +export const useAverageScoreQuery = () => { + return useQuery({ + queryKey: ['averageScore'], + queryFn: getAverageScore, + }); +}; diff --git a/src/renderer/src/api/dashboard/useLevelQuery.ts b/src/renderer/src/api/dashboard/useLevelQuery.ts new file mode 100644 index 0000000..333b15a --- /dev/null +++ b/src/renderer/src/api/dashboard/useLevelQuery.ts @@ -0,0 +1,33 @@ +import { useQuery } from '@tanstack/react-query'; +import api from '../api'; +import { LevelResponse } from '../../types/dashboard/level'; + +/** + * 레벨 도달 현황 조회 API + * GET /dashboard/level + */ +const getLevel = async (): Promise => { + const response = await api.get('/dashboard/level'); + const result = response.data; + + if (!result.success) { + throw new Error(result.message || '레벨 조회 실패'); + } + + return result; +}; + +/** + * 레벨 도달 현황 조회 query 훅 + * @example + * const { data, isLoading, error } = useLevelQuery(); + * const level = data?.data.level; + * const current = data?.data.current; + * const required = data?.data.required; + */ +export const useLevelQuery = () => { + return useQuery({ + queryKey: ['level'], + queryFn: getLevel, + }); +}; diff --git a/src/renderer/src/api/dashboard/usePostureGraphQuery.ts b/src/renderer/src/api/dashboard/usePostureGraphQuery.ts new file mode 100644 index 0000000..0949937 --- /dev/null +++ b/src/renderer/src/api/dashboard/usePostureGraphQuery.ts @@ -0,0 +1,34 @@ +import { useQuery } from '@tanstack/react-query'; +import api from '../api'; +import { PostureGraphResponse } from '../../types/dashboard/postureGraph'; + +/** + * 바른 자세 점수 그래프 조회 API (최근 31일) + * GET /dashboard/posture-graph + */ +const getPostureGraph = async (): Promise => { + const response = await api.get( + '/dashboard/posture-graph', + ); + const result = response.data; + + if (!result.success) { + throw new Error(result.message || '자세 그래프 조회 실패'); + } + + return result; +}; + +/** + * 바른 자세 점수 그래프 조회 query 훅 + * @example + * const { data, isLoading, error } = usePostureGraphQuery(); + * const points = data?.data.points; + * // points: { "2025-01-01": 85, "2025-01-02": 92, ... } + */ +export const usePostureGraphQuery = () => { + return useQuery({ + queryKey: ['postureGraph'], + queryFn: getPostureGraph, + }); +}; diff --git a/src/renderer/src/api/login/useLoginMutation.ts b/src/renderer/src/api/login/useLoginMutation.ts index 7f03f7d..7b1a498 100644 --- a/src/renderer/src/api/login/useLoginMutation.ts +++ b/src/renderer/src/api/login/useLoginMutation.ts @@ -27,6 +27,16 @@ export const useLoginMutation = () => { localStorage.setItem('accessToken', res.data.accessToken); localStorage.setItem('refreshToken', res.data.refreshToken); + /* 사용자 정보 조회 후 이름 저장 */ + try { + const userResponse = await api.get('/users/me'); + if (userResponse.data.success && userResponse.data.data.name) { + localStorage.setItem('userName', userResponse.data.data.name); + } + } catch (error) { + console.error('사용자 정보 조회 실패:', error); + } + navigate('/onboarding/init'); }, onError: (error) => { diff --git a/src/renderer/src/api/session/useStopSessionMutation.ts b/src/renderer/src/api/session/useStopSessionMutation.ts index d96398e..7b3819b 100644 --- a/src/renderer/src/api/session/useStopSessionMutation.ts +++ b/src/renderer/src/api/session/useStopSessionMutation.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import api from '../api'; import { SessionActionResponse } from '../../types/main/session'; @@ -29,6 +29,8 @@ const stopSession = async ( * stopSession(sessionId); */ export const useStopSessionMutation = () => { + const queryClient = useQueryClient(); + return useMutation({ mutationFn: stopSession, onSuccess: () => { @@ -42,6 +44,15 @@ export const useStopSessionMutation = () => { // sessionId를 localStorage에서 제거 localStorage.removeItem('sessionId'); + + // 평균 자세 점수 쿼리 갱신 + queryClient.invalidateQueries({ queryKey: ['averageScore'] }); + + // 레벨 쿼리 갱신 + queryClient.invalidateQueries({ queryKey: ['level'] }); + + // 자세 그래프 쿼리 갱신 + queryClient.invalidateQueries({ queryKey: ['postureGraph'] }); }, onError: (error) => { console.error('세션 중단 오류:', error); diff --git a/src/renderer/src/assets/main/averagePosture/step_five_character.png b/src/renderer/src/assets/main/averagePosture/step_five_character.png new file mode 100644 index 0000000..4727a51 Binary files /dev/null and b/src/renderer/src/assets/main/averagePosture/step_five_character.png differ diff --git a/src/renderer/src/assets/main/averagePosture/step_four_character.png b/src/renderer/src/assets/main/averagePosture/step_four_character.png new file mode 100644 index 0000000..405d9c1 Binary files /dev/null and b/src/renderer/src/assets/main/averagePosture/step_four_character.png differ diff --git a/src/renderer/src/assets/main/averagePosture/step_one_character.png b/src/renderer/src/assets/main/averagePosture/step_one_character.png new file mode 100644 index 0000000..ff05a4d Binary files /dev/null and b/src/renderer/src/assets/main/averagePosture/step_one_character.png differ diff --git a/src/renderer/src/assets/main/averagePosture/step_three_character.png b/src/renderer/src/assets/main/averagePosture/step_three_character.png new file mode 100644 index 0000000..dbe38e8 Binary files /dev/null and b/src/renderer/src/assets/main/averagePosture/step_three_character.png differ diff --git a/src/renderer/src/assets/main/averagePosture/step_two_character.png b/src/renderer/src/assets/main/averagePosture/step_two_character.png new file mode 100644 index 0000000..33a1809 Binary files /dev/null and b/src/renderer/src/assets/main/averagePosture/step_two_character.png differ diff --git a/src/renderer/src/pages/Main/MainPage.tsx b/src/renderer/src/pages/Main/MainPage.tsx index f42b159..904cd12 100644 --- a/src/renderer/src/pages/Main/MainPage.tsx +++ b/src/renderer/src/pages/Main/MainPage.tsx @@ -11,7 +11,7 @@ import { useCameraStore } from '../../store/useCameraStore'; import { usePostureStore } from '../../store/usePostureStore'; import { MetricData } from '../../types/main/session'; import AttendacePanel from './components/AttendacePanel'; -import AveragePosturePanel from './components/AveragePosturePanel'; +import AveragePosturePanel from './components/AveragePosture/AveragePosturePanel'; import HighlightsPanel from './components/HighlightsPanel'; import MainHeader from './components/MainHeader'; import MiniRunningPanel from './components/MiniRunningPanel'; diff --git a/src/renderer/src/pages/Main/components/AverageGraph/data.ts b/src/renderer/src/pages/Main/components/AverageGraph/data.ts deleted file mode 100644 index 029a850..0000000 --- a/src/renderer/src/pages/Main/components/AverageGraph/data.ts +++ /dev/null @@ -1,50 +0,0 @@ -export type AverageGraphDatum = { - periodLabel: string; - score: number; -}; - -/* 주간 데이터 (최근 7일)*/ -export const WEEKLY_DATA: AverageGraphDatum[] = [ - { periodLabel: '1', score: 30 }, - { periodLabel: '2', score: 51 }, - { periodLabel: '3', score: 48 }, - { periodLabel: '4', score: 72 }, - { periodLabel: '5', score: 75 }, - { periodLabel: '6', score: 55 }, - { periodLabel: '7', score: 71 }, -]; - -/*월간 데이터 (최근 31일) */ -export const MONTHLY_DATA: AverageGraphDatum[] = [ - { periodLabel: '1', score: 30 }, - { periodLabel: '2', score: 51 }, - { periodLabel: '3', score: 48 }, - { periodLabel: '4', score: 72 }, - { periodLabel: '5', score: 75 }, - { periodLabel: '6', score: 55 }, - { periodLabel: '7', score: 71 }, - { periodLabel: '8', score: 76 }, - { periodLabel: '9', score: 80 }, - { periodLabel: '10', score: 82 }, - { periodLabel: '11', score: 85 }, - { periodLabel: '12', score: 83 }, - { periodLabel: '13', score: 81 }, - { periodLabel: '14', score: 79 }, - { periodLabel: '15', score: 84 }, - { periodLabel: '16', score: 86 }, - { periodLabel: '17', score: 88 }, - { periodLabel: '18', score: 85 }, - { periodLabel: '19', score: 0 }, - { periodLabel: '20', score: 89 }, - { periodLabel: '21', score: 86 }, - { periodLabel: '22', score: 84 }, - { periodLabel: '23', score: 82 }, - { periodLabel: '24', score: 80 }, - { periodLabel: '25', score: 83 }, - { periodLabel: '26', score: 85 }, - { periodLabel: '27', score: 87 }, - { periodLabel: '28', score: 88 }, - { periodLabel: '29', score: 90 }, - { periodLabel: '30', score: 89 }, - { periodLabel: '31', score: 91 }, -]; diff --git a/src/renderer/src/pages/Main/components/AverageGraph/hooks/useAverageGraphChart.ts b/src/renderer/src/pages/Main/components/AverageGraph/hooks/useAverageGraphChart.ts index 70c8aab..f3648cf 100644 --- a/src/renderer/src/pages/Main/components/AverageGraph/hooks/useAverageGraphChart.ts +++ b/src/renderer/src/pages/Main/components/AverageGraph/hooks/useAverageGraphChart.ts @@ -1,10 +1,12 @@ import { useEffect, useMemo, useState } from 'react'; -import { getColor } from '../../../../../utils/getColor'; -import { MONTHLY_DATA, WEEKLY_DATA, type AverageGraphDatum } from '../data'; +import { getColor } from '@utils/getColor'; +import { usePostureGraphQuery } from '@api/dashboard/usePostureGraphQuery'; + +type AverageGraphDatum = { + periodLabel: string; + score: number; +}; -{ - /* 주간/월간 */ -} export type AverageGraphPeriod = 'weekly' | 'monthly'; type ChartConfig = { @@ -21,6 +23,8 @@ export function useAverageGraphChart(activePeriod: AverageGraphPeriod) { document.documentElement.classList.contains('dark'), ); + const { data: apiData } = usePostureGraphQuery(); + /* html의 class 속성 변경될 때마다 콜백 실행(다크모드 감지) */ useEffect(() => { const observer = new MutationObserver(() => { @@ -42,7 +46,23 @@ export function useAverageGraphChart(activePeriod: AverageGraphPeriod) { '#ffbf00', ); - const data = activePeriod === 'weekly' ? WEEKLY_DATA : MONTHLY_DATA; + /* API 데이터를 그래프 형식으로 변환 */ + let data: AverageGraphDatum[] = []; + if (apiData?.data?.points) { + const points = apiData.data.points; + const sortedEntries = Object.entries(points).sort(([dateA], [dateB]) => + dateA.localeCompare(dateB), + ); + + /* 주간: 최근 7일, 월간: 전체 31일 */ + const slicedEntries = + activePeriod === 'weekly' ? sortedEntries.slice(-7) : sortedEntries; + + data = slicedEntries.map(([date, score]) => ({ + periodLabel: new Date(date).getDate().toString(), + score, + })); + } /* 최댓값 100 */ const domainMax = 100; @@ -58,7 +78,7 @@ export function useAverageGraphChart(activePeriod: AverageGraphPeriod) { gridColor: gridColorValue, yAxisTicks: ticks, }; - }, [activePeriod, isDark]); + }, [activePeriod, isDark, apiData]); return chartConfig; } diff --git a/src/renderer/src/pages/Main/components/AveragePosture/AveragePosturePanel.tsx b/src/renderer/src/pages/Main/components/AveragePosture/AveragePosturePanel.tsx new file mode 100644 index 0000000..09135f6 --- /dev/null +++ b/src/renderer/src/pages/Main/components/AveragePosture/AveragePosturePanel.tsx @@ -0,0 +1,45 @@ +import { useAverageScoreQuery } from '../../../../api/dashboard/useAverageScoreQuery'; +import { LEVEL_INFO, getLevel } from './levelConfig'; + +const AveragePosturePanel = () => { + const { data, isLoading } = useAverageScoreQuery(); + const score = data?.data.score ?? 0; + const level = getLevel(score); + const levelInfo = LEVEL_INFO[level - 1]; + + return ( +
+
+

+ 평균 자세 점수 + + {isLoading ? '-' : `${score}점`} + + + 목 평균 기울기 {levelInfo.tilt} +
+ 예상 하중 {levelInfo.weight} +
+

+

+ + {levelInfo.name} + + {levelInfo.name} +

+
+ +
+

+ Step. {level} +

+
+
+ ); +}; + +export default AveragePosturePanel; diff --git a/src/renderer/src/pages/Main/components/AveragePosture/levelConfig.ts b/src/renderer/src/pages/Main/components/AveragePosture/levelConfig.ts new file mode 100644 index 0000000..2b60c6b --- /dev/null +++ b/src/renderer/src/pages/Main/components/AveragePosture/levelConfig.ts @@ -0,0 +1,61 @@ +import stepOneCharacter from '../../../../assets/main/averagePosture/step_one_character.png'; +import stepTwoCharacter from '../../../../assets/main/averagePosture/step_two_character.png'; +import stepThreeCharacter from '../../../../assets/main/averagePosture/step_three_character.png'; +import stepFourCharacter from '../../../../assets/main/averagePosture/step_four_character.png'; +import stepFiveCharacter from '../../../../assets/main/averagePosture/step_five_character.png'; + +export interface LevelInfo { + level: number; + name: string; + tilt: string; + weight: string; + character: string; +} + +// 점수에 따른 레벨 정보 +export const LEVEL_INFO: LevelInfo[] = [ + { + level: 1, + name: '뽀각거부기', + tilt: '약 55–60°', + weight: '약 26–27 kg', + character: stepOneCharacter, + }, + { + level: 2, + name: '꾸부정 거북기', + tilt: '약 40–45°', + weight: '약 20–22 kg', + character: stepTwoCharacter, + }, + { + level: 3, + name: '아기기린', + tilt: '약 25–30°', + weight: '약 16–18 kg', + character: stepThreeCharacter, + }, + { + level: 4, + name: '쑥쑥기린', + tilt: '약 10–15°', + weight: '약 10–12 kg', + character: stepFourCharacter, + }, + { + level: 5, + name: '꼿꼿기린', + tilt: '약 0–5°', + weight: '약 5–6 kg', + character: stepFiveCharacter, + }, +]; + +// 점수에 따른 레벨 계산 +export const getLevel = (score: number): number => { + if (score < 35) return 1; + if (score < 55) return 2; + if (score < 72) return 3; + if (score < 88) return 4; + return 5; +}; diff --git a/src/renderer/src/pages/Main/components/AveragePosturePanel.tsx b/src/renderer/src/pages/Main/components/AveragePosturePanel.tsx deleted file mode 100644 index f3ac1f7..0000000 --- a/src/renderer/src/pages/Main/components/AveragePosturePanel.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import LevelMarker from '../../../assets/main/downward_caret.svg?react'; -import Character from '../../../assets/main/level1_character.svg?react'; - -const AveragePosturePanel = () => { - return ( -
-
-

- 평균 자세 점수 - 47점 - - 목 평균 기울기 약 23.1도 -
- 예상 부하 하중 약 6kg -
-

-

- - 쑥쑥 기린 - - -

-
- -
-

- Level -

-
- -
-
- {Array.from({ length: 5 }).map((_, index) => ( - - ))} -
-
-
- ); -}; - -export default AveragePosturePanel; diff --git a/src/renderer/src/pages/Main/components/TotalDistancePanel.tsx b/src/renderer/src/pages/Main/components/TotalDistancePanel.tsx index 8f7b515..07c9fe1 100644 --- a/src/renderer/src/pages/Main/components/TotalDistancePanel.tsx +++ b/src/renderer/src/pages/Main/components/TotalDistancePanel.tsx @@ -1,13 +1,27 @@ import { PannelHeader } from '@ui/PannelHeader/PannelHeader'; import AchivementMedal from '../../../assets/main/achivement_meadl.svg?react'; +import { useLevelQuery } from '../../../api/dashboard/useLevelQuery'; const TotalDistance = () => { + const { data, isLoading } = useLevelQuery(); + + const level = data?.data.level ?? 1; + const current = data?.data.current ?? 0; + const required = data?.data.required ?? 1000; + const progressPercentage = (current / required) * 100; + return (
- LV.2 거부기까지 + + {isLoading ? '로딩 중...' : `LV.${level + 1} `} +

- 400 - / 1,200m + + {isLoading ? '-' : current.toLocaleString()} + + + / {isLoading ? '-' : required.toLocaleString()}m +

{/*게이지 바*/}
@@ -16,16 +30,18 @@ const TotalDistance = () => { {/*진행바 */}
- {Array.from({ length: 4 }, (_, i) => i * (1200 / 3)).map((value) => ( - {value} - ))} + {Array.from({ length: 4 }, (_, i) => i * (required / 3)).map( + (value) => ( + {Math.floor(value).toLocaleString()} + ), + )}
); diff --git a/src/renderer/src/pages/Onboarding/OnboardingInitPage.tsx b/src/renderer/src/pages/Onboarding/OnboardingInitPage.tsx index 2af2f0d..5eaeb83 100644 --- a/src/renderer/src/pages/Onboarding/OnboardingInitPage.tsx +++ b/src/renderer/src/pages/Onboarding/OnboardingInitPage.tsx @@ -26,9 +26,9 @@ const OnboardinInitPage = () => { }; return ( -
-
-
+
+
+
{ + const userName = localStorage.getItem('userName') || '사용자'; + return (

- 안녕하세요! Username님의 자세 건강을 책임질 AI 파트너, 거부기린이에요. + 안녕하세요! {userName}님의 자세 건강을 책임질 AI 파트너, 거부기린이에요.

diff --git a/src/renderer/src/types/dashboard/averageScore.ts b/src/renderer/src/types/dashboard/averageScore.ts new file mode 100644 index 0000000..4a0c67c --- /dev/null +++ b/src/renderer/src/types/dashboard/averageScore.ts @@ -0,0 +1,11 @@ +export interface AverageScoreData { + score: number; +} + +export interface AverageScoreResponse { + timestamp: string; + success: boolean; + data: AverageScoreData; + code: string; + message: string | null; +} diff --git a/src/renderer/src/types/dashboard/level.ts b/src/renderer/src/types/dashboard/level.ts new file mode 100644 index 0000000..9c4e91e --- /dev/null +++ b/src/renderer/src/types/dashboard/level.ts @@ -0,0 +1,13 @@ +export interface LevelData { + level: number; + current: number; + required: number; +} + +export interface LevelResponse { + timestamp: string; + success: boolean; + data: LevelData; + code: string; + message: string | null; +} diff --git a/src/renderer/src/types/dashboard/postureGraph.ts b/src/renderer/src/types/dashboard/postureGraph.ts new file mode 100644 index 0000000..b793259 --- /dev/null +++ b/src/renderer/src/types/dashboard/postureGraph.ts @@ -0,0 +1,11 @@ +export interface PostureGraphData { + points: Record; +} + +export interface PostureGraphResponse { + timestamp: string; + success: boolean; + data: PostureGraphData; + code: string; + message: string | null; +} diff --git a/src/renderer/tsconfig.json b/src/renderer/tsconfig.json index f6d7590..6d8b6fe 100644 --- a/src/renderer/tsconfig.json +++ b/src/renderer/tsconfig.json @@ -23,7 +23,9 @@ "baseUrl": ".", "paths": { "@ui/*": ["./src/components/*"], - "@assets/*": ["./src/assets/*"] + "@assets/*": ["./src/assets/*"], + "@api/*": ["./src/api/*"], + "@utils/*": ["./src/utils/*"] } }, "include": [ diff --git a/vite.config.mts b/vite.config.mts index a5a3c9a..66135f5 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -19,6 +19,10 @@ export default defineConfig({ '@ui': path.resolve(__dirname, 'src/renderer/src/components'), '@assets/': path.resolve(__dirname, 'src/renderer/src/assets') + '/', '@assets': path.resolve(__dirname, 'src/renderer/src/assets'), + '@api/': path.resolve(__dirname, 'src/renderer/src/api') + '/', + '@api': path.resolve(__dirname, 'src/renderer/src/api'), + '@utils/': path.resolve(__dirname, 'src/renderer/src/utils') + '/', + '@utils': path.resolve(__dirname, 'src/renderer/src/utils'), ui: path.resolve(__dirname, 'src/renderer/src/components'), }, },