commit 8df1161023f79f9357b7dd086956ab8118e44c70 Author: R0m1k3 Date: Thu Apr 16 09:53:36 2026 +0200 feat: initialize Podcastic project foundation - setup backend and frontend diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..d059e0b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(rtk git:*)", + "Bash(rtk npm:*)" + ] + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0d2df15 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# Backend Configuration +NODE_ENV=development +PORT=5000 +API_URL=http://localhost:5000 + +# MongoDB Configuration +MONGODB_URI=mongodb://mongo:27017/podcastic +MONGODB_USER=podcastic +MONGODB_PASSWORD=podcastic_dev + +# Redis Configuration +REDIS_URL=redis://redis:6379 + +# JWT Configuration +JWT_SECRET=your_super_secret_jwt_key_change_in_production +JWT_EXPIRES_IN=7d +JWT_REFRESH_EXPIRES_IN=30d + +# Podcast API (PodcastIndex) +PODCAST_INDEX_API_KEY=your_podcast_index_api_key +PODCAST_INDEX_API_SECRET=your_podcast_index_secret + +# Frontend Configuration +VITE_API_URL=http://localhost:5000/api diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74a193d --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Dependencies +node_modules/ +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Environment +.env +.env.local +.env.*.local + +# Build outputs +dist/ +build/ +*.tsbuildinfo + +# Development +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Testing +coverage/ +.nyc_output/ + +# Docker +.dockerignore diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..67ca84a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,133 @@ + +# RTK (Rust Token Killer) - Token-Optimized Commands + +## Golden Rule + +**Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use. + +**Important**: Even in command chains with `&&`, use `rtk`: +```bash +# โŒ Wrong +git add . && git commit -m "msg" && git push + +# โœ… Correct +rtk git add . && rtk git commit -m "msg" && rtk git push +``` + +## RTK Commands by Workflow + +### Build & Compile (80-90% savings) +```bash +rtk cargo build # Cargo build output +rtk cargo check # Cargo check output +rtk cargo clippy # Clippy warnings grouped by file (80%) +rtk tsc # TypeScript errors grouped by file/code (83%) +rtk lint # ESLint/Biome violations grouped (84%) +rtk prettier --check # Files needing format only (70%) +rtk next build # Next.js build with route metrics (87%) +``` + +### Test (90-99% savings) +```bash +rtk cargo test # Cargo test failures only (90%) +rtk vitest run # Vitest failures only (99.5%) +rtk playwright test # Playwright failures only (94%) +rtk test # Generic test wrapper - failures only +``` + +### Git (59-80% savings) +```bash +rtk git status # Compact status +rtk git log # Compact log (works with all git flags) +rtk git diff # Compact diff (80%) +rtk git show # Compact show (80%) +rtk git add # Ultra-compact confirmations (59%) +rtk git commit # Ultra-compact confirmations (59%) +rtk git push # Ultra-compact confirmations +rtk git pull # Ultra-compact confirmations +rtk git branch # Compact branch list +rtk git fetch # Compact fetch +rtk git stash # Compact stash +rtk git worktree # Compact worktree +``` + +Note: Git passthrough works for ALL subcommands, even those not explicitly listed. + +### GitHub (26-87% savings) +```bash +rtk gh pr view # Compact PR view (87%) +rtk gh pr checks # Compact PR checks (79%) +rtk gh run list # Compact workflow runs (82%) +rtk gh issue list # Compact issue list (80%) +rtk gh api # Compact API responses (26%) +``` + +### JavaScript/TypeScript Tooling (70-90% savings) +```bash +rtk pnpm list # Compact dependency tree (70%) +rtk pnpm outdated # Compact outdated packages (80%) +rtk pnpm install # Compact install output (90%) +rtk npm run + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..89c71ea --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,36 @@ +{ + "name": "podcastic-frontend", + "version": "1.0.0", + "description": "Podcastic - Multi-user podcast streaming application frontend", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.16.0", + "@tanstack/react-query": "^5.1.0", + "zustand": "^4.4.1", + "@heroicons/react": "^2.0.18", + "axios": "^1.5.0", + "lucide-react": "^0.292.0" + }, + "devDependencies": { + "@types/react": "^18.2.28", + "@types/react-dom": "^18.2.13", + "@typescript-eslint/eslint-plugin": "^6.7.5", + "@typescript-eslint/parser": "^6.7.5", + "@vitejs/plugin-react": "^4.1.0", + "autoprefixer": "^10.4.16", + "eslint": "^8.50.0", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.3", + "typescript": "^5.2.2", + "vite": "^5.0.0" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..f4e7734 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,45 @@ +import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import './styles/globals.css'; + +// Placeholder pages +const Dashboard = () => ( +
+

Dashboard - Coming Soon

+

Latest episodes will appear here

+
+); + +const Login = () => ( +
+
+

Podcastic

+

Sign in to start listening

+ + +
+
+); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 1000 * 60 * 5, // 5 minutes + gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime) + }, + }, +}); + +export default function App() { + return ( + + + + } /> + } /> + } /> + + + + ); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..e63eef4 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css new file mode 100644 index 0000000..65f0c7f --- /dev/null +++ b/frontend/src/styles/globals.css @@ -0,0 +1,110 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +* { + @apply box-border; +} + +html { + scroll-behavior: smooth; +} + +body { + @apply bg-light-50 text-light-900 antialiased; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', + Arial, sans-serif; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + @apply w-2 h-2; +} + +::-webkit-scrollbar-track { + @apply bg-light-100; +} + +::-webkit-scrollbar-thumb { + @apply bg-light-300 rounded-full hover:bg-light-400; +} + +/* Apple TV Style Glassmorphism */ +.glass { + @apply backdrop-blur-xl bg-white/30 border border-white/20 rounded-2xl; +} + +.glass-dark { + @apply backdrop-blur-xl bg-black/30 border border-white/10 rounded-2xl; +} + +/* Glossy effect with shadows */ +.glossy { + @apply shadow-soft; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.glossy:hover { + @apply shadow-lg; + transform: translateY(-2px); +} + +/* Smooth gradient backgrounds */ +.gradient-light { + background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%); +} + +.gradient-primary { + background: linear-gradient(135deg, #3b82f6 0%, #6366f1 100%); +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in { + animation: fadeIn 0.3s ease-in-out; +} + +.animate-slide-up { + animation: slideUp 0.3s ease-out; +} + +/* Custom components */ +.btn-primary { + @apply px-6 py-3 rounded-lg bg-blue-500 text-white font-medium hover:bg-blue-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed; +} + +.btn-secondary { + @apply px-6 py-3 rounded-lg bg-light-200 text-light-900 font-medium hover:bg-light-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed; +} + +.card { + @apply bg-white rounded-2xl p-6 shadow-soft; +} + +.input { + @apply w-full px-4 py-3 rounded-lg border border-light-200 bg-white focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors; +} + +/* Loading spinner */ +.spinner { + @apply inline-block h-4 w-4 animate-spin rounded-full border-2 border-solid border-current border-r-transparent; +} diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..e0cc8db --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,42 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + colors: { + light: { + 50: '#f9fafb', + 100: '#f3f4f6', + 200: '#e5e7eb', + 300: '#d1d5db', + 400: '#9ca3af', + 500: '#6b7280', + 600: '#4b5563', + 700: '#374151', + 800: '#1f2937', + 900: '#111827', + } + }, + backdropFilter: { + 'none': 'none', + 'blur': 'blur(10px)', + }, + fontFamily: { + sans: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', 'sans-serif'], + }, + borderRadius: { + xl: '1rem', + '2xl': '1.5rem', + '3xl': '2rem', + }, + boxShadow: { + 'soft': '0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03)', + 'glow': '0 0 30px rgba(59, 130, 246, 0.1)', + } + }, + }, + plugins: [], +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..3fb09ff --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..1714950 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:5000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + } + } + }, + build: { + outDir: 'dist', + sourcemap: true + } +})