Compare commits

...

13 Commits

Author SHA1 Message Date
f319f2d772 Merge branch 'main' of https://git.byeori.cloud/admin/ocr-nextjs
merge
2026-03-25 23:17:14 +09:00
bc8dc79295 이미지 인풋박스 설정 2026-03-25 22:08:55 +09:00
root
93c00298e3 add 2026-02-27 18:15:36 +09:00
root
32d29845cc kakaots 추가 2026-02-27 18:14:21 +09:00
root
5a91cca94f 로그인 프론트 구조 변경 2026-02-27 18:13:28 +09:00
fd8ea31fd8 프론트 수정 2026-02-02 23:14:53 +09:00
root
1c1a4383e1 서버에 이미지 전송 2026-01-28 19:14:32 +09:00
root
48f706cc42 파일 업로드 수정, css 고치기 2026-01-25 17:10:02 +09:00
497ac11f26 로그인여부 확인하는 요청 2025-12-29 18:03:43 +09:00
root
e6c532faae add tailwind 2025-12-26 20:51:37 +09:00
root
540d32a570 add 2025-12-26 20:18:08 +09:00
root
13094084d4 add main 2025-12-26 20:17:17 +09:00
root
ba4bffc28e 링크는 내부여서 외부인 a로 바꿈 2025-12-17 16:59:50 +09:00
14 changed files with 578 additions and 19 deletions

194
package-lock.json generated
View File

@@ -19,7 +19,9 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4",
"autoprefixer": "^10.4.23",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"typescript": "^5"
}
},
@@ -671,6 +673,13 @@
"tailwindcss": "4.1.11"
}
},
"node_modules/@tailwindcss/node/node_modules/tailwindcss": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
@@ -917,6 +926,13 @@
"tailwindcss": "4.1.11"
}
},
"node_modules/@tailwindcss/postcss/node_modules/tailwindcss": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.7.tgz",
@@ -949,6 +965,43 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
"version": "10.4.23",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
"integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.28.1",
"caniuse-lite": "^1.0.30001760",
"fraction.js": "^5.3.4",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
@@ -959,6 +1012,50 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.11",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
"integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
},
"bin": {
"browserslist": "cli.js"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -983,9 +1080,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
"version": "1.0.30001761",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz",
"integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==",
"funding": [
{
"type": "opencollective",
@@ -999,7 +1096,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/chownr": {
"version": "3.0.0",
@@ -1112,6 +1210,13 @@
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.267",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
"dev": true,
"license": "ISC"
},
"node_modules/enhanced-resolve": {
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
@@ -1166,6 +1271,16 @@
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -1200,6 +1315,20 @@
"node": ">= 6"
}
},
"node_modules/fraction.js": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -1768,6 +1897,13 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"dev": true,
"license": "MIT"
},
"node_modules/oauth": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
@@ -1831,6 +1967,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -1840,6 +1977,13 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/preact": {
"version": "10.26.9",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.26.9.tgz",
@@ -1999,10 +2143,11 @@
}
},
"node_modules/tailwindcss": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"dev": true
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
"dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.2",
@@ -2054,6 +2199,37 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",

View File

@@ -20,7 +20,9 @@
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4",
"autoprefixer": "^10.4.23",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,88 @@
.wrapper {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
min-height: 100vh;
width: 100%;
padding: 1rem;
}
.fileInput {
display: none;
}
.row {
width: 100%;
max-width: 520px;
display: flex;
gap: 0.5rem;
align-items: center;
}
.textInput {
flex: 1;
height: 2.75rem;
padding: 0.65rem 0.75rem;
border: 1px solid #cbd5e1;
border-radius: 0.55rem;
background-color: white;
color: #334155;
font-size: 0.95rem;
outline: none;
}
.textInput:focus {
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
border-color: #2563eb;
}
.btn {
height: 2.75rem;
padding: 0.6rem 1rem;
border-radius: 0.55rem;
background-color: #6366F1;
color: #ffffff;
font-weight: 700;
cursor: pointer;
}
.btn:hover {
background-color: #4F46E5;
}
.btnLoading {
background-color: #A5B4FC;
cursor: wait;
}
.messageArea {
width: 100%;
max-width: 520px;
margin-top: 0.75rem;
text-align: left;
}
.errorMsg {
margin: 0.25rem 0 0;
color: #dc2626;
}
.successMsg {
margin: 0.25rem 0 0;
color: #16a34a;
}
.previewContainer {
margin-bottom: 1.5rem;
display: flex;
justify-content: center;
}
.previewImage {
width: 200px;
height: 120px;
border-radius: 0.55rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
object-fit: cover;
}

View File

@@ -0,0 +1,160 @@
"use client"
import { useCallback, useRef, useState, useEffect } from 'react'
import styles from './ImgInputForm.module.css'
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
const ImgInputForm = () => {
const fileRef = useRef<HTMLInputElement | null>(null)
const [fileName, setFileName] = useState('')
const [preview, setPreview] = useState<string | null>(null)
const [showPreview, setShowPreview] = useState(false)
const [isDragging, setIsDragging] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState<string | null>(null)
//process.env.NEXT_PUBLIC_API_URL ||
const API_BASE = 'http://localhost:9001'
useEffect(() => {
const checkAuth = async () => {
try {
const res = await fetch(`${API_BASE}/auth/check-token`, {
method: 'GET',
credentials: 'include',
})
if (!res.ok) throw new Error('인증 실패')
console.log('사용자 인증 성공')
} catch (err) {
console.error('사용자 인증 실패:', (err as Error).message)
setError('로그인이 필요합니다.')
}
}
checkAuth()
}, [])
const handleFile = useCallback((file: File) => {
setError(null)
setSuccess(null)
if (!file.type.startsWith('image/')) {
setError('이미지 파일만 업로드할 수 있습니다.')
return
}
if (file.size > MAX_FILE_SIZE) {
setError('파일 크기는 5MB 이하만 업로드 가능합니다.')
return
}
setFileName(file.name)
setPreview(URL.createObjectURL(file))
setShowPreview(false)
// keep the selected file in the hidden input
if (fileRef.current) {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
fileRef.current.files = dataTransfer.files
}
}, [])
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const file = e.target.files?.[0]
if (file) handleFile(file)
}
const onClickPick = () => fileRef.current?.click()
const onDrop: React.DragEventHandler<HTMLDivElement> = (e) => {
e.preventDefault()
setIsDragging(false)
const file = e.dataTransfer.files?.[0]
if (file) handleFile(file)
}
const onDragOver: React.DragEventHandler<HTMLDivElement> = (e) => {
e.preventDefault()
setIsDragging(true)
}
const onDragLeave: React.DragEventHandler<HTMLDivElement> = () => {
setIsDragging(false)
}
const upload = async () => {
setError(null)
setSuccess(null)
const files = fileRef.current?.files
if (!files || files.length === 0) {
setError('업로드할 파일을 선택해주세요.')
return
}
const file = files[0]
if (!file.type.startsWith('image/')) {
setError('이미지 파일만 업로드할 수 있습니다.')
return
}
setLoading(true)
setShowPreview(false)
try {
const fd = new FormData()
fd.append('image', file)
const res = await fetch(`${API_BASE}/img/get-img`, {
method: 'POST',
body: fd,
credentials: 'include'
})
if (!res.ok) throw new Error('업로드 실패')
setSuccess('업로드 성공')
setShowPreview(true)
} catch (err) {
setError((err as Error).message || '업로드 중 오류가 발생했습니다')
} finally {
setLoading(false)
}
}
return (
<>
<div className={styles.wrapper}>
<input
ref={fileRef}
type="file"
accept="image/*"
className={styles.fileInput}
onChange={onChange}
/>
{preview && (
<div className={styles.previewContainer}>
<img src={preview} alt="selected preview" className={styles.previewImage} />
</div>
)}
<div className={styles.row}>
<input
type="text"
readOnly
value={fileName}
onClick={onClickPick}
placeholder="선택된 파일 없음"
className={styles.textInput}
/>
<button
onClick={upload}
disabled={loading || !fileName}
className={`${styles.btn} ${loading ? styles.btnLoading : styles.btnPrimary}`}
>
{loading ? '업로드 중...' : '업로드'}
</button>
</div>
{error && <p className={`${styles.errorMsg}`}>{error}</p>}
{success && <p className={styles.successMsg}>{success}</p>}
</div>
</>
)
}
export default ImgInputForm

View File

@@ -6,15 +6,15 @@ import Link from "next/link";
const LoginForm = () => {
const requestUrl = 'https://kauth.kakao.com/oauth/authorize?client_id=a1d6afef2d4508a10a498b7069f67496&redirect_uri=http://localhost:9001/login/oauth-kakao-authorize&response_type=code'
const requestUrl = 'https://kauth.kakao.com/oauth/authorize?client_id=a1d6afef2d4508a10a498b7069f67496&redirect_uri=http://localhost:9001/oauth/oauth-kakao-authorize&response_type=code'
return (
<>
<Link href={requestUrl}>
<a href={requestUrl}>
<button type="button">
<img src={loginButton.src} alt="카카오톡 로그인" />
</button>
</Link>
</a>
</>
)
}

View File

@@ -3,6 +3,7 @@
:root {
--background: #ffffff;
--foreground: #171717;
height: 100%;
}
@theme inline {

View File

@@ -23,10 +23,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<html lang="ko" className="h-full">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased h-full min-h-screen`} >
{children}
</body>
</html>

View File

@@ -1,5 +1,14 @@
'use client';
import LoginForm from "../components/LoginForm";
import LoginForm from '../components/LoginForm';
<LoginForm/>
const LoginPage = () => {
return (
<div>
<h1></h1>
<LoginForm />
</div>
);
};
export default LoginPage;

10
src/app/main/page.tsx Normal file
View File

@@ -0,0 +1,10 @@
import ImgInputForm from '../components/ImgInputForm'
const MainPage = () => {
return <ImgInputForm />
}
export default MainPage

View File

@@ -0,0 +1,51 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
const OAuthCallback = () => {
const router = useRouter();
const [error, setError] = useState<string | null>(null);
const [message, setMessage] = useState<string | null>(null);
useEffect(() => {
const handleOAuthCallback = async () => {
console.log('OAuth 콜백 처리 중...');
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const error = urlParams.get('error');
if (error) {
setError('카카오 로그인 중 오류가 발생했습니다.');
return;
}
if (code) {
try {
const res = await fetch('/api/oauth/kakao', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
if (!res.ok) throw new Error('서버 요청 실패');
const data = await res.json();
setMessage(`로그인 성공: ${data.message}`);
} catch (err) {
setError((err as Error).message || '로그인 처리 중 오류가 발생했습니다.');
}
}
};
handleOAuthCallback();
}, []);
return (
<div>
{error && <p style={{ color: 'red' }}>{error}</p>}
{message && <p>{message}</p>}
</div>
);
};
export default OAuthCallback;

6
src/lib/Auth.ts Normal file
View File

@@ -0,0 +1,6 @@
import axios from "axios";
export const callServer = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
withCredentials: true
})

View File

@@ -0,0 +1,33 @@
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method Not Allowed' });
}
const { code } = req.body;
if (!code) {
return res.status(400).json({ message: 'Authorization code is missing' });
}
try {
const tokenResponse = await fetch('https://kauth.kakao.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.KAKAO_CLIENT_ID!,
redirect_uri: process.env.KAKAO_REDIRECT_URI!,
code,
}),
});
if (!tokenResponse.ok) throw new Error('Failed to fetch access token');
const tokenData = await tokenResponse.json();
res.status(200).json({ message: '카카오 로그인 성공', token: tokenData });
} catch (err) {
res.status(500).json({ message: (err as Error).message });
}
}

12
src/utils/Login.ts Normal file
View File

@@ -0,0 +1,12 @@
import { callServer } from "@/lib/Auth";
import { redirect } from "next/navigation";
export async function isLogin() {
const res = callServer.get('/login/get-user-info').then(res => {
const userInfo = res.data
console.log(userInfo)
if (!userInfo) {
redirect('/login')
}
})
}

13
tailwind.config.js Normal file
View File

@@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}