commit fb25027bdcfc48a622640269cd43ec8bd7d955be Author: kyounggu Date: Mon Jun 22 21:36:51 2026 +0900 first commit diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..708c4e7 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,31 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /usr/src/app + +COPY package*.json ./ +RUN npm install + +COPY . . + +# Prisma client 생성 및 TypeScript 빌드 +RUN npx prisma generate +RUN npm run build + +# Production stage +FROM node:20-alpine + +WORKDIR /usr/src/app + +COPY package*.json ./ +RUN npm install --only=production + +COPY --from=builder /usr/src/app/dist ./dist +COPY --from=builder /usr/src/app/node_modules/.prisma ./node_modules/.prisma +COPY --from=builder /usr/src/app/node_modules/@prisma/client ./node_modules/@prisma/client +COPY --from=builder /usr/src/app/prisma ./prisma + +EXPOSE 4000 + +# Prisma 마이그레이션 실행 후 백엔드 서버 실행 +CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"] diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..8316f04 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,30 @@ +{ + "name": "kindergarten-diary-backend", + "version": "1.0.0", + "description": "Kindergarten Diary AI Generator Backend", + "main": "dist/index.js", + "scripts": { + "dev": "nodemon src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate deploy" + }, + "dependencies": { + "@google/generative-ai": "^0.1.1", + "@prisma/client": "^5.10.2", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "mysql2": "^3.9.2" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^20.11.24", + "nodemon": "^3.1.0", + "prisma": "^5.10.2", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" + } +} diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..f09116f --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,17 @@ +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "linux-musl-openssl-3.0.x"] +} + +model Diary { + id Int @id @default(autoincrement()) + childName String + originalContent String @db.Text + generatedContent String @db.Text + createdAt DateTime @default(now()) +} diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 0000000..13d0f83 --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,130 @@ +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import { PrismaClient } from '@prisma/client'; +import { GoogleGenAI } from '@google/generative-ai'; + +// 환경 변수 설정 +dotenv.config(); +dotenv.config({ path: '.env.production' }); + +const app = express(); +const port = process.env.PORT || 4000; +const prisma = new PrismaClient(); + +app.use(cors()); +app.use(express.json()); + +// 헬스체크 API +app.get('/api/health', (req: Request, res: Response) => { + res.status(200).json({ status: 'ok', message: '백엔드 서버가 정상 구동 중입니다.' }); +}); + +// AI 일지 작성 API +app.post('/api/diary/generate', async (req: Request, res: Response) => { + const { content, keywords, childName } = req.body; + + // AI 설정 값 읽기 (env.production에 설정 예정) + const apiKey = process.env.AI_API_KEY; + const modelName = process.env.AI_MODEL || 'gemini-1.5-flash'; + + if (!apiKey) { + return res.status(500).json({ + error: 'AI_API_KEY 환경 변수가 설정되지 않았습니다. .env.production 파일을 확인해 주세요.' + }); + } + + if (!content && (!keywords || keywords.length === 0)) { + return res.status(400).json({ error: '일지 내용 또는 키워드를 입력해 주세요.' }); + } + + try { + const ai = new GoogleGenAI({ apiKey }); + const model = ai.getGenerativeModel({ model: modelName }); + + const prompt = ` +당신은 유치원 교사입니다. 교사가 작성한 간단한 메모나 키워드를 바탕으로 학부모님께 보낼 정성스럽고 자연스러운 알림장(유치원 일지)을 작성해 주세요. + +[아이 이름] +${childName || '아이'} + +[교사 작성 내용] +${content || ''} + +[키워드] +${keywords ? keywords.join(', ') : ''} + +[작성 지침] +1. 정중하고 따뜻한 어조(해요체)로 작성해 주세요. +2. 아이의 활동을 구체적이고 긍정적으로 묘사해 주세요. +3. 너무 길지 않고 가독성 좋게 단락을 나누어 작성해 주세요. +4. 학부모님께 드리는 인사와 마무리 멘트를 포함해 주세요. +`; + + const result = await model.generateContent(prompt); + const responseText = result.response.text(); + + return res.status(200).json({ + success: true, + originalContent: content, + generatedContent: responseText, + modelUsed: modelName + }); + + } catch (error: any) { + console.error('AI 생성 에러:', error); + return res.status(500).json({ + error: 'AI 일지 작성 중 에러가 발생했습니다.', + details: error.message + }); + } +}); + +// 일지 저장 API (DB 연동) +app.post('/api/diary/save', async (req: Request, res: Response) => { + const { childName, originalContent, generatedContent } = req.body; + + try { + const newDiary = await prisma.diary.create({ + data: { + childName: childName || '미지정', + originalContent: originalContent || '', + generatedContent: generatedContent || '', + } + }); + + return res.status(201).json({ + success: true, + message: '일지가 데이터베이스에 정상적으로 저장되었습니다.', + data: newDiary + }); + } catch (error: any) { + console.error('DB 저장 에러:', error); + return res.status(500).json({ + error: '데이터베이스 저장 중 에러가 발생했습니다.', + details: error.message + }); + } +}); + +// 전체 일지 조회 API +app.get('/api/diary/list', async (req: Request, res: Response) => { + try { + const diaries = await prisma.diary.findMany({ + orderBy: { + createdAt: 'desc' + } + }); + return res.status(200).json({ success: true, data: diaries }); + } catch (error: any) { + console.error('DB 조회 에러:', error); + return res.status(500).json({ + error: '데이터베이스 조회 중 에러가 발생했습니다.', + details: error.message + }); + } +}); + +app.listen(port, () => { + console.log(`서버가 http://localhost:${port} 에서 구동 중입니다.`); +}); diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..3bbb0fc --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "CommonJS", + "lib": ["ES2022"], + "allowJs": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "baseUrl": "./", + "paths": { + "*": ["node_modules/*", "src/*"] + }, + "esModuleInterop": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..94aa5b4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +version: '3.8' + +services: + db: + image: mysql:8.0 + container_name: kid-diary-db + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: kid_diary + MYSQL_PASSWORD: kidpassword + MYSQL_USER: kiduser + ports: + - "3306:3306" + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpassword"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: kid-diary-backend + restart: always + ports: + - "4000:4000" + environment: + - DATABASE_URL=mysql://kiduser:kidpassword@db:3306/kid_diary + - PORT=4000 + depends_on: + db: + condition: service_healthy + env_file: + - ./backend/.env.production + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: kid-diary-frontend + restart: always + ports: + - "80:80" + depends_on: + - backend + +volumes: + db_data: diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..2b7520e --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,20 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /usr/src/app + +COPY package*.json ./ +RUN npm install + +COPY . . +RUN npm run build + +# Production stage +FROM nginx:alpine + +COPY --from=builder /usr/src/app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..7dbf7eb --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..ef614d2 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..0fca6f0 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..8d2766e --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,15 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..70b0913 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,30 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.6", + "react-dom": "^19.2.6" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^24.12.3", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.3.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.6.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.59.2", + "vite": "^8.0.12" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..f90339d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..a66b5ef --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,122 @@ +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from './assets/vite.svg' +import heroImg from './assets/hero.png' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + + return ( + <> +
+
+ + React logo + Vite logo +
+
+

Get started

+

+ Edit src/App.tsx and save to test HMR +

+
+ +
+ +
+ +
+
+ +

Documentation

+

Your questions, answered

+ +
+
+ +

Connect with us

+

Join the Vite community

+ +
+
+ +
+
+ + ) +} + +export default App diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/frontend/src/assets/hero.png differ diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..5fb3313 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,111 @@ +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +#root { + width: 1126px; + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +body { + margin: 0; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code, +.counter { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..7f42e5f --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +})