diff --git a/.env.example b/.env.example index 78748e2..4ec494b 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ # Example environment variables DATABASE_URL=postgres://user:password@localhost:5432/cloud_costs?sslmode=disable JWT_SECRET=your_jwt_secret_here -NEXT_PUBLIC_API_URL=http://localhost:8000 \ No newline at end of file +NEXT_PUBLIC_API_URL=http://localhost:8080 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7e14a8c..c580ce8 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,13 @@ Thumbs.db # Build outputs dist/ -build/ \ No newline at end of file +build/ +*.tsbuildinfo + +# Claude / AI tooling (never commit) +.claude/ +CLAUDE.md +.qodo/ + +# Local-only compose overrides +docker-compose.override.yml \ No newline at end of file diff --git a/frontend/app/accounts/page.tsx b/frontend/app/accounts/page.tsx index 087b40e..6627043 100644 --- a/frontend/app/accounts/page.tsx +++ b/frontend/app/accounts/page.tsx @@ -3,11 +3,16 @@ import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import api from '@/lib/api' +import { withMinDuration } from '@/lib/utils' +import { AccountRowSkeleton } from '@/components/ui/Skeleton' +import { Spinner } from '@/components/ui/Spinner' +import { ThemeToggle } from '@/components/theme/ThemeToggle' export default function AccountsPage() { const router = useRouter() const [activeTab, setActiveTab] = useState('aws') const [loading, setLoading] = useState(false) + const [accountsLoading, setAccountsLoading] = useState(true) const [error, setError] = useState('') const [success, setSuccess] = useState('') const [existingAccounts, setExistingAccounts] = useState([]) @@ -50,10 +55,12 @@ export default function AccountsPage() { const fetchAccounts = async () => { try { - const response = await api.get('/accounts') + const response = await withMinDuration(api.get('/accounts'), 1000) setExistingAccounts(response.data) } catch (error) { console.error('Failed to fetch accounts:', error) + } finally { + setAccountsLoading(false) } } @@ -145,31 +152,33 @@ export default function AccountsPage() { const getProviderBadgeColor = (provider: string) => { switch(provider) { - case 'aws': return 'bg-orange-500/20 text-orange-400' - case 'gcp': return 'bg-blue-500/20 text-blue-400' - case 'azure': return 'bg-blue-600/20 text-blue-400' - default: return 'bg-gray-500/20 text-gray-400' + case 'aws': return 'bg-orange-500/20 text-orange-700 dark:text-orange-400' + case 'gcp': return 'bg-blue-500/20 text-blue-600 dark:text-blue-400' + case 'azure': return 'bg-cyan-600/20 text-cyan-700 dark:text-cyan-400' + default: return 'bg-gray-500/20 text-gray-600 dark:text-gray-400' } } return ( -
-
-
-

Cloud Cost Optimization Hub

-
- +
+
+
+

Cloud Cost Optimization Hub

+
+ Dashboard - + Recommendations + @@ -177,25 +186,34 @@ export default function AccountsPage() {
-
+
{/* Existing Accounts Section */} - {existingAccounts.length > 0 && ( + {accountsLoading ? (
-

Connected Accounts

+

Connected Accounts

+
+ {Array.from({ length: 2 }).map((_, i) => ( + + ))} +
+
+ ) : existingAccounts.length > 0 && ( +
+

Connected Accounts

{existingAccounts.map((account) => ( -
+
{account.provider.toUpperCase()}
-

{account.name}

-

Account ID: {account.account_id}

+

{account.name}

+

Account ID: {account.account_id}

{account.status} @@ -206,35 +224,38 @@ export default function AccountsPage() { )} {/* Add Account Section with Tabs */} -
-
-
+
+
+