diff --git a/.biome.json b/.biome.json new file mode 100644 index 0000000..5b87575 --- /dev/null +++ b/.biome.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { "useExhaustiveDependencies": "error" }, + "suspicious": { "noExplicitAny": "error" } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always" + } + } +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..e77713f --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,4 @@ +// ESLint の設定ファイル +module.exports = { + extends: ['next/core-web-vitals'], +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3fd0b67 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + lint-and-typecheck: + name: Lint and Type Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + - name: Run Biome Lint & Format Check + run: npm run lint:biome + - name: Run TypeScript Type Check + run: npm run build + build: + name: Build Project + runs-on: ubuntu-latest + needs: lint-and-typecheck + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + - name: Build Next.js application + run: npm run build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..2e5efde --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,30 @@ +name: Deploy to Cloudflare Pages + +on: + push: + branches: [main] + +jobs: + publish: + name: Publish to Cloudflare Pages + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install dependencies + run: npm ci + - name: Build with OpenNext + run: npm run opennext:build + - name: Publish to Cloudflare Pages + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + projectName: "prototyping-base-app-router" diff --git a/.husky/_/husky.sh b/.husky/_/husky.sh new file mode 100755 index 0000000..af8e43c --- /dev/null +++ b/.husky/_/husky.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# Husky による Git フックの共通処理 +export PATH="$PATH:$(npm bin)" diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..0499f72 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx biome check --apply . +npx biome format --write . diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8685eda --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,35 @@ +# LLM開発ルール + +## LLMで生成したいこと +- UI生成 +- 機能実装 +- デプロイ + +## 絶対に守るべきこと +- シンプルなアーキテクチャ +- 日本語でのドキュメント・コードコメント +- 最新バージョン技術の使用 +- 秘匿情報を含まない公開リポジトリ +- 汎用的な記述 + +## 検討事項 +- 技術スタック +- DB +- UIデザイン +- デプロイ先 + +## リポジトリに必要なもの +- LLM用rulesファイル (このファイル) +- packages.json +- README.md +- Linter設定 +- Formatter設定 +- Typescript設定 +- TailwindCSS設定 +- envファイル +- CloudflareでのNext.jsアプリ作成方法 + +## 不要なもの +- サンプルアプリケーション +- 具体的な実装 +- ハッカソン情報 diff --git a/README.md b/README.md index ab51738..012dd85 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ ## 技術スタック -- **フロントエンド**: Next.js, React -- **スタイリング**: CSS Modules または styled-components -- **バックエンド**: API routes (Next.jsのサーバレスAPI機能を使用) +- **フロントエンド**: Next.js(App Router) と React +- **スタイリング**: TailwindCSS +- **言語/ツール**: TypeScript, Biome, Husky +- **デプロイ**: OpenNext を利用した Cloudflare Pages へのデプロイを想定 ## プロジェクト構成 @@ -44,3 +45,10 @@ ```bash npm install npm run dev +``` + +アプリを静的にエクスポートする場合は以下を実行してください: + +```bash +npm run opennext:build +``` diff --git a/components/Alert.js b/components/Alert.js deleted file mode 100644 index 47dec6e..0000000 --- a/components/Alert.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -const Alert = ({ message, isVisible }) => { - if (!isVisible) return null; - - return ( -
-

{message}

-
- ); -}; - -export default Alert; diff --git a/components/SettingsForm.js b/components/SettingsForm.js deleted file mode 100644 index e4db371..0000000 --- a/components/SettingsForm.js +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useState } from 'react'; - -const SettingsForm = ({ onSave }) => { - const [timerSettings, setTimerSettings] = useState({ total: 0, section: 0 }); - - const handleSubmit = (event) => { - event.preventDefault(); - onSave(timerSettings); - }; - - return ( -
- - - -
- ); -}; - -export default SettingsForm; diff --git a/components/Timer.js b/components/Timer.js deleted file mode 100644 index cd642fc..0000000 --- a/components/Timer.js +++ /dev/null @@ -1,20 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -const Timer = ({ initialTime }) => { - const [timeLeft, setTimeLeft] = useState(initialTime); - - useEffect(() => { - if (timeLeft > 0) { - const timerId = setTimeout(() => setTimeLeft(timeLeft - 1), 1000); - return () => clearTimeout(timerId); - } - }, [timeLeft]); - - return ( -
-

{timeLeft}

-
- ); -}; - -export default Timer; diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..de7cb05 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: このファイルは自動生成されます。 + diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..15c0849 --- /dev/null +++ b/next.config.js @@ -0,0 +1,7 @@ +// Next.js の設定ファイル +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +module.exports = nextConfig; diff --git a/package.json b/package.json index 7f5dcdb..6f61c3e 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,37 @@ { - "name": "interview-timer-app", - "version": "1.0.0", - "description": "A timer application for managing interview sessions", - "main": "index.js", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start" - }, - "dependencies": { - "next": "latest", - "react": "latest", - "react-dom": "latest" - } + "name": "prototyping-base-app-router", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "lint:biome": "biome check --apply .", + "format:biome": "biome format --write .", + "prepare": "husky install", + "cf-typegen": "wrangler types --env cloudflare-pages > src/types/cloudflare.d.ts", + "opennext:build": "opennext build" + }, + "dependencies": { + "next": "latest", + "react": "latest", + "react-dom": "latest" + }, + "devDependencies": { + "@biomejs/biome": "latest", + "@cloudflare/workers-types": "latest", + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "autoprefixer": "latest", + "eslint": "latest", + "eslint-config-next": "latest", + "husky": "latest", + "opennext": "latest", + "postcss": "latest", + "tailwindcss": "latest", + "typescript": "latest", + "wrangler": "latest" + } } diff --git a/pages/_app.js b/pages/_app.js deleted file mode 100644 index 5f55999..0000000 --- a/pages/_app.js +++ /dev/null @@ -1,8 +0,0 @@ -// pages/_app.js -import '../styles/globals.css' - -function MyApp({ Component, pageProps }) { - return -} - -export default MyApp; diff --git a/pages/index.js b/pages/index.js deleted file mode 100644 index 1e15f1c..0000000 --- a/pages/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import Head from 'next/head'; -import Timer from '../components/Timer'; - -export default function Home() { - return ( -
- - Interview Timer - - - - -
-

Welcome to the Interview Timer

- -
- -
-

© 2024 Interview Timer

-
-
- ); -} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..0912b86 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,7 @@ +// PostCSS の設定ファイル +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..49a46bf --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,4 @@ +/* TailwindCSS の基本スタイルを読み込み */ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..de87420 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,11 @@ +import '../app/globals.css'; +import type { ReactNode } from 'react'; + +// ルートレイアウト +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..d03c5f3 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,11 @@ +import Timer from '../components/Timer'; + +// トップページ +export default function Page() { + return ( +
+

Interview Timer App

+ +
+ ); +} diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx new file mode 100644 index 0000000..54ff4fe --- /dev/null +++ b/src/components/Alert.tsx @@ -0,0 +1,9 @@ +// アラート表示用コンポーネント +export default function Alert({ message, isVisible }: { message: string; isVisible: boolean }) { + if (!isVisible) return null; + return ( +
+

{message}

+
+ ); +} diff --git a/src/components/SettingsForm.tsx b/src/components/SettingsForm.tsx new file mode 100644 index 0000000..863d74c --- /dev/null +++ b/src/components/SettingsForm.tsx @@ -0,0 +1,38 @@ +import { useState, FormEvent } from 'react'; + +interface Settings { + total: number; + section: number; +} + +// タイマー設定フォーム +export default function SettingsForm({ onSave }: { onSave: (settings: Settings) => void }) { + const [timerSettings, setTimerSettings] = useState({ total: 0, section: 0 }); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + onSave(timerSettings); + }; + + return ( +
+ + + +
+ ); +} diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx new file mode 100644 index 0000000..e5f5653 --- /dev/null +++ b/src/components/Timer.tsx @@ -0,0 +1,19 @@ +import { useState, useEffect } from 'react'; + +// シンプルなタイマーコンポーネント +export default function Timer({ initialTime }: { initialTime: number }) { + const [timeLeft, setTimeLeft] = useState(initialTime); + + useEffect(() => { + if (timeLeft > 0) { + const timerId = setTimeout(() => setTimeLeft(timeLeft - 1), 1000); + return () => clearTimeout(timerId); + } + }, [timeLeft]); + + return ( +
+

{timeLeft}

+
+ ); +} diff --git a/src/styles/globals.css b/src/styles/globals.css new file mode 100644 index 0000000..f43b90c --- /dev/null +++ b/src/styles/globals.css @@ -0,0 +1,8 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* 必要に応じてグローバルスタイルを追加 */ +body { + font-family: sans-serif; +} diff --git a/src/types/cloudflare.d.ts b/src/types/cloudflare.d.ts new file mode 100644 index 0000000..ea18a73 --- /dev/null +++ b/src/types/cloudflare.d.ts @@ -0,0 +1,3 @@ +// src/types/cloudflare.d.ts +// このファイルは `npm run cf-typegen` により自動生成されます。 +// 手動で編集しないでください。 diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..ea5aabf --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,16 @@ +/** + * TailwindCSS の設定ファイル + * @type {import('tailwindcss').Config} + */ +module.exports = { + content: [ + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}' + ], + theme: { + extend: { + // カスタムテーマ設定 + }, + }, + plugins: [], +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..16a006c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [{ "name": "next" }], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..09829aa --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,11 @@ +# wrangler.toml +name = "prototyping-base-app-router" +main = ".open-next/worker.js" +compatibility_date = "2025-03-25" +compatibility_flags = ["nodejs_compat"] + +[site] +bucket = ".open-next/assets" + +[build] +command = "npm run opennext:build"