diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..81d60b3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,58 @@
+# SPDX-FileCopyrightText: 2025 SecPal Contributors
+# SPDX-License-Identifier: CC0-1.0
+
+# Dependencies
+node_modules/
+
+# Build artifacts
+dist/
+build/
+.vite/
+.turbo/
+
+# Environment & Secrets
+.env
+.env.*
+!.env.example
+secrets/
+credentials/
+*.key
+*.pem
+*.p12
+*.pfx
+*.keystore
+*.jks
+.npmrc
+.yarnrc
+
+# Testing
+coverage/
+.nyc_output/
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+Desktop.ini
+
+# Logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Temporary files
+*.tmp
+*.temp
+.cache/
+
+# Storybook
+storybook-static/
diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 0000000..1dd8622
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,6 @@
+{
+ "default": true,
+ "MD013": false,
+ "MD033": false,
+ "MD041": false
+}
diff --git a/.markdownlintignore b/.markdownlintignore
new file mode 100644
index 0000000..4890ada
--- /dev/null
+++ b/.markdownlintignore
@@ -0,0 +1,3 @@
+node_modules/
+LICENSES/
+CHANGELOG.md
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..d35af50
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: 2025 SecPal
+# SPDX-License-Identifier: CC0-1.0
+
+node_modules/
+dist/
+build/
+coverage/
+package-lock.json
+pnpm-lock.yaml
+yarn.lock
+.vite/
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..115ffb9
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": false,
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false,
+ "arrowParens": "always",
+ "endOfLine": "lf"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..56a5e34
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,18 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+
+- Initial repository setup
+- React + TypeScript + Vite configuration
+- Testing setup with Vitest and React Testing Library
+- REUSE 3.3 compliance
+- Pre-commit and pre-push quality gates
+
+[unreleased]: https://github.com/SecPal/frontend/commits/main
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..132c2b6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,5 @@
+This project is licensed under the GNU Affero General Public License v3.0 or later.
+
+See LICENSES/AGPL-3.0-or-later.txt for the full license text.
+
+For third-party licenses, see the LICENSES/ directory.
diff --git a/README.md b/README.md
index 0c9bf2b..0cc5a18 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,208 @@
-# frontend
-React/TypeScript frontend for SecPal platform
+
+
+# SecPal Frontend
+
+[](https://github.com/SecPal/frontend/actions/workflows/reuse.yml)
+[](https://github.com/SecPal/frontend/actions/workflows/license-compatibility.yml)
+[](https://github.com/SecPal/frontend/actions/workflows/quality.yml)
+
+React/TypeScript frontend for the SecPal platform.
+
+## ๐ Prerequisites
+
+- Node.js >= 20.0.0
+- npm >= 10.0.0
+- Git with GPG signing configured
+
+## ๐ Getting Started
+
+### Clone Repository
+
+```bash
+cd ~/code/SecPal
+git clone https://github.com/SecPal/frontend.git
+cd frontend
+```
+
+### โ ๏ธ IMPORTANT: Create Symlinks (DRY Principle)
+
+**Before installing dependencies**, you MUST create symlinks to avoid file duplication:
+
+```bash
+# Navigate to frontend repository
+cd ~/code/SecPal/frontend
+
+# Create symlinks to .github repository (governance files)
+ln -sf ../.github/CONTRIBUTING.md .
+ln -sf ../.github/SECURITY.md .
+ln -sf ../.github/CODE_OF_CONDUCT.md .
+ln -sf ../.github/CODEOWNERS .
+ln -sf ../.github/.editorconfig .editorconfig
+ln -sf ../.github/.gitattributes .gitattributes
+
+# Verify symlinks were created correctly
+file CONTRIBUTING.md # Should show: symbolic link to ../.github/CONTRIBUTING.md
+```
+
+**Why symlinks?** To maintain DRY (Don't Repeat Yourself) principle across repositories. All governance files are centralized in the `.github` repository.
+
+### Install Dependencies
+
+```bash
+npm install
+```
+
+### Setup Pre-Commit Hooks
+
+```bash
+./scripts/setup-pre-commit.sh
+```
+
+## ๐ ๏ธ Development
+
+### Start Development Server
+
+```bash
+npm run dev
+```
+
+### Build for Production
+
+```bash
+npm run build
+```
+
+### Run Tests
+
+```bash
+# Run all tests
+npm test
+
+# Run tests in watch mode
+npm run test:ui
+
+# Generate coverage report
+npm run test:coverage
+```
+
+### Code Quality
+
+```bash
+# Lint code
+npm run lint
+
+# Type checking
+npm run typecheck
+
+# Format code
+npm run format
+
+# Check formatting
+npm run format:check
+```
+
+### Pre-Push Validation
+
+**Before every push**, run the preflight script:
+
+```bash
+./scripts/preflight.sh
+```
+
+This runs:
+
+- โ
Prettier formatting check
+- โ
Markdownlint
+- โ
REUSE compliance
+- โ
ESLint
+- โ
TypeScript type checking
+- โ
Vitest test suite
+- โ
PR size validation (โค600 lines)
+
+## ๐ Project Structure
+
+```
+frontend/
+โโโ src/
+โ โโโ components/ # React components
+โ โโโ hooks/ # Custom hooks
+โ โโโ pages/ # Page components
+โ โโโ services/ # API services
+โ โโโ types/ # TypeScript types
+โ โโโ utils/ # Utility functions
+โ โโโ App.tsx # Root component
+โ โโโ main.tsx # Entry point
+โโโ public/ # Static assets
+โโโ tests/ # Test files
+โโโ .github/ # GitHub workflows and templates
+โโโ scripts/ # Build and utility scripts
+โโโ package.json # Dependencies and scripts
+```
+
+## ๐งช Testing Guidelines
+
+- **Coverage target:** 80%+ for new code, 100% for critical paths
+- **TDD mandatory:** Write failing test first, implement, refactor
+- Use `@testing-library/react` for component testing
+- Mock API calls with MSW (Mock Service Worker)
+- Test user-visible behavior, not implementation
+
+## ๐ Security
+
+- **Secret scanning:** Enabled with push protection
+- **Dependabot:** Daily security updates (04:00 CET)
+- **SAST:** CodeQL analysis on pull requests
+- **Never commit:** API keys, passwords, tokens, `.env` files
+
+See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
+
+## ๐ Contributing
+
+See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
+
+### Branch Naming Convention
+
+- `feature/` - New features
+- `fix/` - Bug fixes
+- `docs/` - Documentation
+- `refactor/` - Code refactoring
+- `test/` - Test additions/fixes
+- `chore/` - Maintenance
+- `spike/` - Exploration (no TDD required, cannot merge to main)
+
+### Commit Messages
+
+Follow [Conventional Commits](https://www.conventionalcommits.org/):
+
+```
+feat: add user authentication
+fix: resolve memory leak in dashboard
+docs: update API integration guide
+test: add tests for login form
+```
+
+## ๐ License
+
+**AGPL-3.0-or-later** - See [LICENSE](LICENSE) for details.
+
+This project is REUSE 3.3 compliant. All files contain SPDX license headers.
+
+## ๐ Related Repositories
+
+- [API](https://github.com/SecPal/api) - Laravel backend
+- [Contracts](https://github.com/SecPal/contracts) - OpenAPI specifications
+- [.github](https://github.com/SecPal/.github) - Organization defaults
+
+## ๐ Support
+
+- **Issues:** [GitHub Issues](https://github.com/SecPal/frontend/issues)
+- **Security:** See [SECURITY.md](SECURITY.md)
+- **Code of Conduct:** [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
+
+---
+
+**Maintained by:** SecPal Team
+**Last Updated:** October 2025
diff --git a/REUSE.toml b/REUSE.toml
new file mode 100644
index 0000000..abcaac8
--- /dev/null
+++ b/REUSE.toml
@@ -0,0 +1,68 @@
+# SPDX-FileCopyrightText: 2025 SecPal
+# SPDX-License-Identifier: CC0-1.0
+
+version = 1
+SPDX-FileCopyrightText = "SecPal Contributors"
+
+[[annotations]]
+path = "src/**"
+SPDX-License-Identifier = "AGPL-3.0-or-later"
+
+[[annotations]]
+path = "public/**"
+SPDX-License-Identifier = "AGPL-3.0-or-later"
+
+[[annotations]]
+path = "package.json"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = "package-lock.json"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = "tsconfig*.json"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = "vite.config.ts"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = ".prettierrc.json"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = ".prettierignore"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = ".gitignore"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = "README.md"
+SPDX-FileCopyrightText = "2025 SecPal"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = ".github/**"
+SPDX-FileCopyrightText = "2025 SecPal"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = "CHANGELOG.md"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = ".markdownlint*.json"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = ".markdownlintignore"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = "scripts/**"
+SPDX-FileCopyrightText = "2025 SecPal Contributors"
+SPDX-License-Identifier = "MIT"
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..764de3b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ SecPal
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e3bfd77
--- /dev/null
+++ b/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "@secpal/frontend",
+ "version": "0.0.1",
+ "description": "React/TypeScript frontend for SecPal platform",
+ "private": true,
+ "type": "module",
+ "author": "SecPal",
+ "license": "AGPL-3.0-or-later",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/SecPal/frontend.git"
+ },
+ "homepage": "https://github.com/SecPal/frontend#readme",
+ "bugs": {
+ "url": "https://github.com/SecPal/frontend/issues"
+ },
+ "keywords": [
+ "secpal",
+ "frontend",
+ "react",
+ "typescript",
+ "vite"
+ ],
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "typecheck": "tsc --noEmit",
+ "test": "vitest",
+ "test:ui": "vitest --ui",
+ "test:coverage": "vitest --coverage",
+ "format": "prettier --write '**/*.{md,yml,yaml,json,ts,tsx,js,jsx}'",
+ "format:check": "prettier --check '**/*.{md,yml,yaml,json,ts,tsx,js,jsx}'"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@testing-library/react": "^16.1.0",
+ "@testing-library/user-event": "^14.5.2",
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@typescript-eslint/eslint-plugin": "^8.26.0",
+ "@typescript-eslint/parser": "^8.26.0",
+ "@vitejs/plugin-react": "^4.3.4",
+ "@vitest/ui": "^3.0.5",
+ "eslint": "^9.18.0",
+ "eslint-plugin-react-hooks": "^5.1.0",
+ "eslint-plugin-react-refresh": "^0.4.18",
+ "jsdom": "^25.0.1",
+ "prettier": "^3.4.2",
+ "typescript": "^5.7.3",
+ "vite": "^6.0.6",
+ "vitest": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=20.0.0",
+ "npm": ">=10.0.0"
+ }
+}
diff --git a/scripts/preflight.sh b/scripts/preflight.sh
new file mode 100644
index 0000000..446aae9
--- /dev/null
+++ b/scripts/preflight.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+# SPDX-FileCopyrightText: 2025 SecPal Contributors
+# SPDX-License-Identifier: MIT
+
+set -euo pipefail
+
+ROOT_DIR="$(git rev-parse --show-toplevel)"
+cd "$ROOT_DIR"
+
+# Auto-detect default branch (fallback to main)
+BASE="$(git remote show origin 2>/dev/null | sed -n '/HEAD branch/s/.*: //p')"
+[ -z "${BASE:-}" ] && BASE="main"
+
+echo "Using base branch: $BASE"
+
+# Fetch base branch for PR size check (failure is handled later)
+git fetch origin "$BASE" 2>/dev/null || true
+
+# 0) Formatting & Compliance
+FORMAT_EXIT=0
+if command -v npx >/dev/null 2>&1; then
+ npx --yes prettier --check '**/*.{md,yml,yaml,json,ts,tsx,js,jsx}' || FORMAT_EXIT=1
+ npx --yes markdownlint-cli2 '**/*.md' || FORMAT_EXIT=1
+fi
+# Workflow linting (part of documented gates)
+if [ -d .github/workflows ]; then
+ if command -v actionlint >/dev/null 2>&1; then
+ actionlint || FORMAT_EXIT=1
+ else
+ echo "Warning: .github/workflows found but actionlint not installed - skipping workflow lint" >&2
+ fi
+fi
+if command -v reuse >/dev/null 2>&1; then
+ reuse lint || FORMAT_EXIT=1
+fi
+if [ "$FORMAT_EXIT" -ne 0 ]; then
+ echo "Formatting/compliance checks failed. Fix issues above." >&2
+ exit 1
+fi
+
+# 1) Node.js / React / TypeScript
+if [ -f pnpm-lock.yaml ] && command -v pnpm >/dev/null 2>&1; then
+ pnpm install --frozen-lockfile
+ pnpm run --if-present lint
+ pnpm run --if-present typecheck
+ pnpm run --if-present test
+elif [ -f package-lock.json ] && command -v npm >/dev/null 2>&1; then
+ npm ci
+ npm audit --audit-level=high || {
+ echo "High or critical severity vulnerabilities detected by npm audit. Please address the issues above before continuing." >&2
+ exit 1
+ }
+ npm run --if-present lint
+ npm run --if-present typecheck
+ npm run --if-present test
+elif [ -f yarn.lock ] && command -v yarn >/dev/null 2>&1; then
+ yarn install --frozen-lockfile
+ if command -v jq >/dev/null 2>&1; then
+ jq -e '.scripts.lint' package.json >/dev/null 2>&1 && yarn lint
+ jq -e '.scripts.typecheck' package.json >/dev/null 2>&1 && yarn typecheck
+ jq -e '.scripts.test' package.json >/dev/null 2>&1 && yarn test
+ elif command -v node >/dev/null 2>&1; then
+ node -e "process.exit(require('./package.json').scripts?.lint ? 0 : 1)" && yarn lint
+ node -e "process.exit(require('./package.json').scripts?.typecheck ? 0 : 1)" && yarn typecheck
+ node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)" && yarn test
+ else
+ echo "Warning: jq and node not found - attempting to run yarn scripts (failures will be ignored)" >&2
+ yarn lint 2>/dev/null || true
+ yarn typecheck 2>/dev/null || true
+ yarn test 2>/dev/null || true
+ fi
+fi
+
+# 2) Check PR size locally (against BASE)
+if ! git rev-parse -q --verify "origin/$BASE" >/dev/null 2>&1; then
+ echo "Warning: Cannot verify base branch origin/$BASE - skipping PR size check." >&2
+ echo "Tip: Run 'git fetch origin $BASE' to enable PR size checking." >&2
+else
+ MERGE_BASE=$(git merge-base "origin/$BASE" HEAD 2>/dev/null)
+ if [ -z "$MERGE_BASE" ]; then
+ echo "Warning: Cannot determine merge base with origin/$BASE. Skipping PR size check." >&2
+ else
+ # Use --numstat for locale-independent parsing (sum insertions + deletions)
+ CHANGED=$(git diff --numstat "$MERGE_BASE"..HEAD 2>/dev/null | awk '{ins+=$1; del+=$2} END {print ins+del+0}')
+ [ -z "$CHANGED" ] && CHANGED=0
+ if [ "$CHANGED" -gt 600 ]; then
+ echo "PR too large ($CHANGED > 600 lines). Please split into smaller slices." >&2
+ exit 2
+ fi
+ echo "Preflight OK ยท Changed lines: $CHANGED"
+ fi
+fi
+
+# All checks passed
+exit 0
diff --git a/scripts/setup-pre-commit.sh b/scripts/setup-pre-commit.sh
new file mode 100644
index 0000000..038c425
--- /dev/null
+++ b/scripts/setup-pre-commit.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# SPDX-FileCopyrightText: 2025 SecPal Contributors
+# SPDX-License-Identifier: MIT
+
+set -euo pipefail
+
+ROOT_DIR="$(git rev-parse --show-toplevel)"
+GIT_HOOKS_DIR="$ROOT_DIR/.git/hooks"
+PROJECT_HOOKS_DIR="$ROOT_DIR/.githooks"
+
+echo "Setting up pre-commit hooks..."
+
+# Create .githooks directory if it doesn't exist
+mkdir -p "$PROJECT_HOOKS_DIR"
+
+# Create pre-push hook
+cat > "$PROJECT_HOOKS_DIR/pre-push" << 'EOF'
+#!/usr/bin/env bash
+# SPDX-FileCopyrightText: 2025 SecPal Contributors
+# SPDX-License-Identifier: MIT
+
+set -euo pipefail
+
+ROOT_DIR="$(git rev-parse --show-toplevel)"
+cd "$ROOT_DIR"
+
+# Check if current branch is spike/*
+BRANCH=$(git rev-parse --abbrev-ref HEAD)
+if [[ "$BRANCH" == spike/* ]]; then
+ echo "Spike branch detected ($BRANCH) - running minimal checks (formatting + REUSE)"
+
+ # Run only formatting and REUSE compliance
+ FORMAT_EXIT=0
+ if command -v npx >/dev/null 2>&1; then
+ npx --yes prettier --check '**/*.{md,yml,yaml,json,ts,tsx,js,jsx}' || FORMAT_EXIT=1
+ npx --yes markdownlint-cli2 '**/*.md' || FORMAT_EXIT=1
+ fi
+ if command -v reuse >/dev/null 2>&1; then
+ reuse lint || FORMAT_EXIT=1
+ fi
+
+ if [ "$FORMAT_EXIT" -ne 0 ]; then
+ echo "Formatting/compliance checks failed. Fix issues above." >&2
+ exit 1
+ fi
+
+ echo "Spike branch checks passed."
+ exit 0
+fi
+
+# For non-spike branches, run full preflight script
+if [ -f "$ROOT_DIR/scripts/preflight.sh" ]; then
+ "$ROOT_DIR/scripts/preflight.sh"
+else
+ echo "Warning: preflight.sh not found - skipping pre-push checks" >&2
+fi
+EOF
+
+chmod +x "$PROJECT_HOOKS_DIR/pre-push"
+
+# Create symlink in .git/hooks
+ln -sf "$PROJECT_HOOKS_DIR/pre-push" "$GIT_HOOKS_DIR/pre-push"
+
+echo "โ
Pre-commit hooks configured successfully"
+echo " - Pre-push hook: Runs preflight.sh before every push"
+echo " - Spike branches: Only formatting + REUSE (tests skipped)"
+echo ""
+echo "To bypass (NOT RECOMMENDED):"
+echo " git push --no-verify"
diff --git a/src/App.test.tsx b/src/App.test.tsx
new file mode 100644
index 0000000..fec8f7d
--- /dev/null
+++ b/src/App.test.tsx
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: 2025 SecPal
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+import { describe, it, expect } from "vitest";
+import { render, screen } from "@testing-library/react";
+import App from "./App";
+
+describe("App", () => {
+ it("renders welcome message", () => {
+ render();
+ expect(screen.getByText(/SecPal Frontend/i)).toBeInTheDocument();
+ });
+
+ it("renders main content", () => {
+ render();
+ expect(
+ screen.getByText(/Welcome to SecPal - Your secure platform/i)
+ ).toBeInTheDocument();
+ });
+});
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..cf3fdc7
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2025 SecPal
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+function App() {
+ return (
+
+
+
+ Welcome to SecPal - Your secure platform
+
+
+ );
+}
+
+export default App;
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..2b1182d
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,44 @@
+/* SPDX-FileCopyrightText: 2025 SecPal */
+/* SPDX-License-Identifier: AGPL-3.0-or-later */
+
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+.app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+header h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..f80f1af
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2025 SecPal
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+import "./index.css";
+
+const rootElement = document.getElementById("root");
+if (!rootElement) {
+ throw new Error("Root element not found");
+}
+
+createRoot(rootElement).render(
+
+
+
+);
diff --git a/tests/setup.ts b/tests/setup.ts
new file mode 100644
index 0000000..eb9431c
--- /dev/null
+++ b/tests/setup.ts
@@ -0,0 +1,4 @@
+// SPDX-FileCopyrightText: 2025 SecPal
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+import "@testing-library/jest-dom";
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..76baccb
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Path aliases */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ },
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..97ede7e
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..c06bc61
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2025 SecPal
+// SPDX-License-Identifier: CC0-1.0
+
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import path from "path";
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "src"),
+ },
+ },
+ test: {
+ globals: true,
+ environment: "jsdom",
+ setupFiles: "./tests/setup.ts",
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "json", "html"],
+ exclude: [
+ "node_modules/",
+ "tests/",
+ "**/*.config.ts",
+ "**/*.d.ts",
+ "**/index.ts",
+ ],
+ },
+ },
+});