diff --git a/web/components.json b/web/components.json index ffe928f..3289f23 100644 --- a/web/components.json +++ b/web/components.json @@ -18,4 +18,4 @@ "hooks": "@/hooks" }, "iconLibrary": "lucide" -} \ No newline at end of file +} diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs index 719cea2..60f7af3 100644 --- a/web/eslint.config.mjs +++ b/web/eslint.config.mjs @@ -1,6 +1,6 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; +import { dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { FlatCompat } from '@eslint/eslintrc'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -10,15 +10,9 @@ const compat = new FlatCompat({ }); const eslintConfig = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), + ...compat.extends('next/core-web-vitals', 'next/typescript'), { - ignores: [ - "node_modules/**", - ".next/**", - "out/**", - "build/**", - "next-env.d.ts", - ], + ignores: ['node_modules/**', '.next/**', 'out/**', 'build/**', 'next-env.d.ts'], }, ]; diff --git a/web/next.config.ts b/web/next.config.ts index e9ffa30..5e891cf 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -1,4 +1,4 @@ -import type { NextConfig } from "next"; +import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ diff --git a/web/package-lock.json b/web/package-lock.json index f5ef042..c5d1abf 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -34,6 +34,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.0", + "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", "tw-animate-css": "^1.3.7", "typescript": "^5" @@ -5881,6 +5882,110 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/web/package.json b/web/package.json index 9851692..740d473 100644 --- a/web/package.json +++ b/web/package.json @@ -35,6 +35,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.5.0", + "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", "tw-animate-css": "^1.3.7", "typescript": "^5" diff --git a/web/postcss.config.mjs b/web/postcss.config.mjs index c7bcb4b..ba720fe 100644 --- a/web/postcss.config.mjs +++ b/web/postcss.config.mjs @@ -1,5 +1,5 @@ const config = { - plugins: ["@tailwindcss/postcss"], + plugins: ['@tailwindcss/postcss'], }; export default config; diff --git a/web/prettier.config.mjs b/web/prettier.config.mjs new file mode 100644 index 0000000..d0997f9 --- /dev/null +++ b/web/prettier.config.mjs @@ -0,0 +1,10 @@ +const prettierConfig = { + plugins: ['prettier-plugin-tailwindcss'], + semi: true, + singleQuote: true, + trailingComma: 'all', + printWidth: 100, + endOfLine: 'auto', +}; + +export default prettierConfig; diff --git a/web/src/app/globals.css b/web/src/app/globals.css index c5b5787..0c13075 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -1,5 +1,5 @@ -@import "tailwindcss"; -@import "tw-animate-css"; +@import 'tailwindcss'; +@import 'tw-animate-css'; @custom-variant dark (&:is(.dark *)); diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index 5925a88..c948bbb 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -1,12 +1,12 @@ -import type { Metadata } from "next"; -import "./globals.css"; -import Link from "next/link"; -import AppHeader from "@/components/app-header"; -import Providers from "./providers"; +import type { Metadata } from 'next'; +import './globals.css'; +import Link from 'next/link'; +import AppHeader from '@/components/app-header'; +import Providers from './providers'; export const metadata: Metadata = { - title: "Web", - description: "SPA Boilereplate", + title: 'Web', + description: 'SPA Boilereplate', }; export default function RootLayout({ diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx index ffaa1b7..8703f87 100644 --- a/web/src/app/login/page.tsx +++ b/web/src/app/login/page.tsx @@ -1,38 +1,32 @@ -"use client"; +'use client'; -import { z } from "zod"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { setToken } from "@/lib/token"; -import { api } from "@/lib/api"; -import { useRouter } from "next/navigation"; -import axios from "axios"; -import { useQueryClient } from "@tanstack/react-query"; -import { MeResponse } from "@/hooks/useMe"; -import Link from "next/link"; +import { z } from 'zod'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { setToken } from '@/lib/token'; +import { api } from '@/lib/api'; +import { useRouter } from 'next/navigation'; +import axios from 'axios'; +import { useQueryClient } from '@tanstack/react-query'; +import { MeResponse } from '@/hooks/useMe'; +import Link from 'next/link'; const LoginSchema = z.object({ - name: z.string().min(1, "이름을 입력하세요."), - password: z.string().min(4, "비밀번호를 입력하세요."), + name: z.string().min(1, '이름을 입력하세요.'), + password: z.string().min(4, '비밀번호를 입력하세요.'), }); type LoginInput = z.infer; export default function LoginPage() { const form = useForm({ resolver: zodResolver(LoginSchema), - defaultValues: { name: "", password: "" }, - mode: "onTouched", + defaultValues: { name: '', password: '' }, + mode: 'onTouched', }); const [error, setError] = useState(null); @@ -44,27 +38,27 @@ export default function LoginPage() { setError(null); setLoading(true); try { - const res = await api.post("/auth/login", values); + const res = await api.post('/auth/login', values); setToken(res.data.access_token); // me 캐시 미리 채워넣기 await queryClient.prefetchQuery({ - queryKey: ["me"], + queryKey: ['me'], queryFn: async (): Promise => { - const res = await api.get("/auth/me"); + const res = await api.get('/auth/me'); return res.data; }, }); - router.replace("/me"); + router.replace('/me'); } catch (e: unknown) { console.log(e); if (axios.isAxiosError(e)) { - setError(e?.message || "로그인 실패"); + setError(e?.message || '로그인 실패'); } else if (e instanceof Error) { setError(e.message); } else { - setError("로그인 실패"); + setError('로그인 실패'); } } finally { setLoading(false); @@ -72,7 +66,7 @@ export default function LoginPage() { }; return ( -
+
@@ -81,21 +75,14 @@ export default function LoginPage() { {/* Name */}
- + ( - + @@ -121,15 +108,13 @@ export default function LoginPage() { /> - {error && ( -

{error}

- )} + {error &&

{error}

}

- 아직 계정이 없으신가요?{" "} + 아직 계정이 없으신가요?{' '} 회원가입 diff --git a/web/src/app/me/page.tsx b/web/src/app/me/page.tsx index 764072b..1f9a8a2 100644 --- a/web/src/app/me/page.tsx +++ b/web/src/app/me/page.tsx @@ -1,8 +1,8 @@ -"use client"; +'use client'; -import Guard from "@/components/guard"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { useMe } from "@/hooks/useMe"; +import Guard from '@/components/guard'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { useMe } from '@/hooks/useMe'; export default function MePage() { const { data, isLoading, error } = useMe(); @@ -13,15 +13,15 @@ export default function MePage() { {error &&

조회 실패

} {!isLoading && !error && data && ( -
+
-
+
{data.name?.[0]}
- {data.name ?? "이름 없음"} -
ID: {data.id}
+ {data.name ?? '이름 없음'} +
ID: {data.id}
diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index a932894..10e812e 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -1,9 +1,9 @@ -import Image from "next/image"; +import Image from 'next/image'; export default function Home() { return ( -
-
+
+
-
    +
    1. - Get started by editing{" "} - + Get started by editing{' '} + src/app/page.tsx .
    2. -
    3. - Save and see your changes instantly. -
    4. +
    5. Save and see your changes instantly.
    -
-