create-bluecopa-react-app 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -28
- package/bin/create-bluecopa-react-app.js +1 -1
- package/package.json +1 -1
- package/templates/latest/.env.example +14 -0
- package/templates/latest/.eslintrc.cjs +42 -0
- package/templates/latest/README.md +264 -0
- package/templates/latest/clean.sh +39 -0
- package/templates/latest/index.html +14 -0
- package/templates/{basic → latest}/package-lock.json +1099 -1084
- package/templates/latest/package.json +61 -0
- package/templates/{basic/postcss.config.js → latest/postcss.config.cjs} +1 -1
- package/templates/latest/public/bluecopa-logo.svg +30 -0
- package/templates/latest/public/favicon-32x32.png +0 -0
- package/templates/latest/public/favicon-96x96.png +0 -0
- package/templates/latest/public/favicon.ico +0 -0
- package/templates/latest/setup.sh +55 -0
- package/templates/latest/src/App.tsx +15 -0
- package/templates/latest/src/components/layout/dashboard-header.tsx +139 -0
- package/templates/latest/src/components/layout/dashboard-layout.tsx +29 -0
- package/templates/latest/src/components/layout/sidebar.tsx +54 -0
- package/templates/latest/src/components/page/dashboard.tsx +1506 -0
- package/templates/latest/src/components/page/navbar.tsx +104 -0
- package/templates/latest/src/components/tables/data-grid.tsx +439 -0
- package/templates/latest/src/components/ui/alert.tsx +59 -0
- package/templates/latest/src/components/ui/avatar.tsx +50 -0
- package/templates/latest/src/components/ui/badge.tsx +36 -0
- package/templates/latest/src/components/ui/bluecopa-logo.tsx +54 -0
- package/templates/{basic → latest}/src/components/ui/button.tsx +5 -11
- package/templates/latest/src/components/ui/dropdown-menu.tsx +200 -0
- package/templates/latest/src/components/ui/input.tsx +24 -0
- package/templates/latest/src/components/ui/label.tsx +23 -0
- package/templates/latest/src/components/ui/select.tsx +29 -0
- package/templates/latest/src/hooks/use-api.ts +55 -0
- package/templates/{basic → latest}/src/index.css +1 -1
- package/templates/{basic → latest}/src/main.tsx +6 -2
- package/templates/latest/src/pages/Dashboard.tsx +13 -0
- package/templates/latest/src/pages/Home.tsx +622 -0
- package/templates/latest/src/providers/query-provider.tsx +48 -0
- package/templates/latest/src/single-spa.tsx +105 -0
- package/templates/{basic/src/types/index.ts → latest/src/types/api.ts} +19 -35
- package/templates/latest/src/vite-env.d.ts +11 -0
- package/templates/{basic → latest}/tailwind.config.js +15 -4
- package/templates/{basic/tsconfig.json → latest/tsconfig.app.json} +5 -10
- package/templates/latest/tsconfig.json +19 -0
- package/templates/latest/vite.config.ts +64 -0
- package/templates/basic/.editorconfig +0 -12
- package/templates/basic/.env.example +0 -10
- package/templates/basic/README.md +0 -213
- package/templates/basic/index.html +0 -13
- package/templates/basic/package.json +0 -68
- package/templates/basic/setup.sh +0 -46
- package/templates/basic/src/App.tsx +0 -95
- package/templates/basic/src/components/dashboard/AdvancedAnalytics.tsx +0 -351
- package/templates/basic/src/components/dashboard/MetricsOverview.tsx +0 -150
- package/templates/basic/src/components/dashboard/TransactionCharts.tsx +0 -215
- package/templates/basic/src/components/dashboard/TransactionTable.tsx +0 -172
- package/templates/basic/src/components/ui/tabs.tsx +0 -53
- package/templates/basic/src/pages/Dashboard.tsx +0 -135
- package/templates/basic/vite.config.ts +0 -13
- /package/templates/{basic → latest}/src/components/ui/card.tsx +0 -0
- /package/templates/{basic → latest}/src/lib/utils.ts +0 -0
- /package/templates/{basic → latest}/tsconfig.node.json +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bluecopa/react-router-template",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React template with React Router for Bluecopa applications with TanStack Query, Radix UI, and Recharts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "tsc && vite build",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 100",
|
|
12
|
+
"lint:fix": "eslint src --ext ts,tsx --fix",
|
|
13
|
+
"type-check": "tsc --noEmit",
|
|
14
|
+
"clean": "rm -rf dist node_modules package-lock.json",
|
|
15
|
+
"serve:federation": "npm run build:federation && npx serve dist -l 8080 --cors",
|
|
16
|
+
"serve:standalone": "npm run build:standalone && npx serve dist-standalone -l 8080 --cors",
|
|
17
|
+
"build:federation": "vite build",
|
|
18
|
+
"build:standalone": "VITE_BUILD_TARGET=standalone vite build --outDir dist-standalone && cp dist-standalone/standalone.html dist-standalone/index.html",
|
|
19
|
+
"build:dev": "vite build --mode development",
|
|
20
|
+
"start:railway": "ls -la && echo \"BUILD_TYPE: $BUILD_TYPE\" && echo \"Serving: dist${BUILD_TYPE:+-$BUILD_TYPE}\" && npx serve -s dist${BUILD_TYPE:+-$BUILD_TYPE} -l $PORT --cors"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@bluecopa/react": "^0.1.3",
|
|
24
|
+
"@radix-ui/react-avatar": "^1.1.0",
|
|
25
|
+
"@radix-ui/react-dropdown-menu": "^2.1.0",
|
|
26
|
+
"@radix-ui/react-select": "^2.1.0",
|
|
27
|
+
"@radix-ui/react-separator": "^1.1.0",
|
|
28
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
29
|
+
"@radix-ui/react-tabs": "^1.1.0",
|
|
30
|
+
"class-variance-authority": "^0.7.0",
|
|
31
|
+
"clsx": "^2.1.0",
|
|
32
|
+
"lucide-react": "^0.445.0",
|
|
33
|
+
"react": "^18.3.0",
|
|
34
|
+
"react-dom": "^18.3.0",
|
|
35
|
+
"react-router-dom": "^6.26.0",
|
|
36
|
+
"recharts": "^2.12.0",
|
|
37
|
+
"single-spa": "^6.0.3",
|
|
38
|
+
"single-spa-react": "^6.0.2",
|
|
39
|
+
"tailwind-merge": "^2.5.0",
|
|
40
|
+
"tailwindcss-animate": "^1.0.7"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@originjs/vite-plugin-federation": "^1.4.1",
|
|
44
|
+
"@types/node": "^24.3.1",
|
|
45
|
+
"@types/react": "^18.3.0",
|
|
46
|
+
"@types/react-dom": "^18.3.0",
|
|
47
|
+
"@types/systemjs": "^6.15.3",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
49
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
50
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
51
|
+
"@vitejs/plugin-react-swc": "^4.0.1",
|
|
52
|
+
"autoprefixer": "^10.4.20",
|
|
53
|
+
"eslint": "^8.57.0",
|
|
54
|
+
"eslint-plugin-react-hooks": "^4.6.2",
|
|
55
|
+
"eslint-plugin-react-refresh": "^0.4.9",
|
|
56
|
+
"postcss": "^8.4.47",
|
|
57
|
+
"tailwindcss": "^3.4.17",
|
|
58
|
+
"typescript": "^5.6.0",
|
|
59
|
+
"vite": "^5.4.20"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1820 1774.9">
|
|
3
|
+
<defs>
|
|
4
|
+
<style>
|
|
5
|
+
.cls-1, .cls-2 {
|
|
6
|
+
fill: #fff;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.cls-3 {
|
|
10
|
+
fill: #3548ff;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.cls-3, .cls-4, .cls-2 {
|
|
14
|
+
isolation: isolate;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cls-4 {
|
|
18
|
+
fill: #8ac2ff;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
</defs>
|
|
22
|
+
<g id="Layer_1-2" data-name="Layer 1">
|
|
23
|
+
<circle class="cls-1" cx="1054.53" cy="779.52" r="386.21"/>
|
|
24
|
+
<g>
|
|
25
|
+
<path class="cls-4" d="M5.91,1004.47L0,1768.96l764.5,5.91c422.08,3.27,767.15-336.52,770.42-758.58,3.26-422.06-336.51-767.14-758.59-770.41C350.68,242.59,9.18,582.4,5.91,1004.47Z"/>
|
|
26
|
+
<path class="cls-3" d="M1820,764.52V0h-764.52C633.38,0,290.95,342.44,290.95,764.52s342.43,764.52,764.52,764.52,764.52-342.44,764.52-764.52Z"/>
|
|
27
|
+
<circle class="cls-2" cx="1055.48" cy="780.55" r="382.26"/>
|
|
28
|
+
</g>
|
|
29
|
+
</g>
|
|
30
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Bluecopa React Router Template Setup Script
|
|
4
|
+
|
|
5
|
+
echo "🚀 Setting up Bluecopa React Router Template..."
|
|
6
|
+
|
|
7
|
+
# Check if Node.js is installed
|
|
8
|
+
if ! command -v node &> /dev/null; then
|
|
9
|
+
echo "❌ Node.js is not installed. Please install Node.js first."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Check if pnpm is installed, prefer it over npm
|
|
14
|
+
if command -v pnpm &> /dev/null; then
|
|
15
|
+
PACKAGE_MANAGER="pnpm"
|
|
16
|
+
echo "📦 Installing dependencies with pnpm..."
|
|
17
|
+
pnpm install
|
|
18
|
+
elif command -v npm &> /dev/null; then
|
|
19
|
+
PACKAGE_MANAGER="npm"
|
|
20
|
+
echo "📦 Installing dependencies with npm..."
|
|
21
|
+
npm install
|
|
22
|
+
else
|
|
23
|
+
echo "❌ No package manager found. Please install npm or pnpm first."
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "🔧 Creating environment file..."
|
|
28
|
+
if [ ! -f .env.local ]; then
|
|
29
|
+
cp .env.example .env.local 2>/dev/null || {
|
|
30
|
+
echo "# Bluecopa API Configuration" > .env.local
|
|
31
|
+
echo "VITE_BLUECOPA_API_TOKEN=your-bluecopa-api-token-here" >> .env.local
|
|
32
|
+
echo "VITE_BLUECOPA_API_URL=https://develop.bluecopa.com/api/v1" >> .env.local
|
|
33
|
+
echo "VITE_BLUECOPA_WORKSPACE_ID=your-workspace-id-here" >> .env.local
|
|
34
|
+
}
|
|
35
|
+
echo "✅ Created .env.local file"
|
|
36
|
+
echo "⚠️ Please update the following variables with your actual values:"
|
|
37
|
+
echo " - VITE_BLUECOPA_API_TOKEN"
|
|
38
|
+
echo " - VITE_BLUECOPA_WORKSPACE_ID"
|
|
39
|
+
echo " - VITE_BLUECOPA_API_URL"
|
|
40
|
+
else
|
|
41
|
+
echo "ℹ️ .env.local already exists"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
echo "🎨 Setting up Tailwind CSS..."
|
|
45
|
+
npx tailwindcss init -p 2>/dev/null || echo "✅ Tailwind already configured"
|
|
46
|
+
|
|
47
|
+
echo "🔍 Running type check..."
|
|
48
|
+
npm run type-check
|
|
49
|
+
|
|
50
|
+
echo "✅ Setup complete!"
|
|
51
|
+
echo ""
|
|
52
|
+
echo "🎉 You can now start the development server:"
|
|
53
|
+
echo " npm run dev"
|
|
54
|
+
echo ""
|
|
55
|
+
echo "📖 Open http://localhost:3000 to view your application"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Routes, Route } from 'react-router-dom'
|
|
2
|
+
import QueryProvider from '@/providers/query-provider'
|
|
3
|
+
import Home from '@/pages/Home'
|
|
4
|
+
import Dashboard from '@/pages/Dashboard'
|
|
5
|
+
|
|
6
|
+
export default function App() {
|
|
7
|
+
return (
|
|
8
|
+
<QueryProvider>
|
|
9
|
+
<Routes>
|
|
10
|
+
<Route path="/" element={<Home />} />
|
|
11
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
12
|
+
</Routes>
|
|
13
|
+
</QueryProvider>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Bell, Search, User, Settings, LogOut, ChevronDown } from 'lucide-react'
|
|
2
|
+
import { Button } from '@/components/ui/button'
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuLabel,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuTrigger,
|
|
10
|
+
} from '@/components/ui/dropdown-menu'
|
|
11
|
+
import { Input } from '@/components/ui/input'
|
|
12
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
13
|
+
import { useUserData } from '@/hooks/use-api'
|
|
14
|
+
|
|
15
|
+
interface DashboardHeaderProps {
|
|
16
|
+
title?: string
|
|
17
|
+
subtitle?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function DashboardHeader({ title, subtitle }: DashboardHeaderProps) {
|
|
21
|
+
const { data: userData, isLoading } = useUserData()
|
|
22
|
+
|
|
23
|
+
// Extract user from the API response structure
|
|
24
|
+
const user = userData?.user
|
|
25
|
+
const organization = userData?.organization
|
|
26
|
+
|
|
27
|
+
// Create display values with proper fallbacks
|
|
28
|
+
const getDisplayName = () => {
|
|
29
|
+
if (!user) return 'Loading...'
|
|
30
|
+
|
|
31
|
+
// If firstName and lastName are the same as email, just use email
|
|
32
|
+
if (user.firstName === user.email && user.lastName === user.email) {
|
|
33
|
+
return user.email
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try to construct name from firstName and lastName
|
|
37
|
+
const fullName = `${user.firstName || ''} ${user.lastName || ''}`.trim()
|
|
38
|
+
return fullName || user.email
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const displayName = getDisplayName()
|
|
42
|
+
const displayRole = organization?.name || 'User' // Use organization name as role
|
|
43
|
+
const displayEmail = user?.email || 'loading@example.com'
|
|
44
|
+
|
|
45
|
+
// Show loading state in user info
|
|
46
|
+
if (isLoading) {
|
|
47
|
+
return (
|
|
48
|
+
<header className="bg-white border-b border-gray-200 px-6 py-4">
|
|
49
|
+
<div className="flex items-center justify-between">
|
|
50
|
+
<div className="flex-1">
|
|
51
|
+
{title && (
|
|
52
|
+
<div>
|
|
53
|
+
<h1 className="text-2xl font-semibold text-gray-900">{title}</h1>
|
|
54
|
+
{subtitle && <p className="text-sm text-gray-500 mt-1">{subtitle}</p>}
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
<div className="flex items-center space-x-4">
|
|
59
|
+
<div className="text-sm text-gray-500">Loading user...</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</header>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<header className="bg-white border-b border-gray-200 px-6 py-4">
|
|
68
|
+
<div className="flex items-center justify-between">
|
|
69
|
+
<div className="flex-1">
|
|
70
|
+
{title && (
|
|
71
|
+
<div>
|
|
72
|
+
<h1 className="text-2xl font-semibold text-gray-900">{title}</h1>
|
|
73
|
+
{subtitle && <p className="text-sm text-gray-500 mt-1">{subtitle}</p>}
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className="flex items-center space-x-4">
|
|
79
|
+
{/* Search */}
|
|
80
|
+
<div className="relative hidden md:block">
|
|
81
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
82
|
+
<Input
|
|
83
|
+
type="search"
|
|
84
|
+
placeholder="Search..."
|
|
85
|
+
className="w-80 pl-10 pr-4"
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Notifications */}
|
|
90
|
+
<Button variant="ghost" size="sm" className="relative">
|
|
91
|
+
<Bell className="h-5 w-5" />
|
|
92
|
+
<span className="absolute -top-1 -right-1 h-4 w-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
|
|
93
|
+
3
|
|
94
|
+
</span>
|
|
95
|
+
</Button>
|
|
96
|
+
|
|
97
|
+
{/* User Menu */}
|
|
98
|
+
<DropdownMenu>
|
|
99
|
+
<DropdownMenuTrigger asChild>
|
|
100
|
+
<Button variant="ghost" className="flex items-center space-x-2 p-2">
|
|
101
|
+
<Avatar className="h-8 w-8">
|
|
102
|
+
<AvatarImage src={undefined} alt={displayName} />
|
|
103
|
+
<AvatarFallback>{displayName.split(' ').map((n: string) => n[0]).join('')}</AvatarFallback>
|
|
104
|
+
</Avatar>
|
|
105
|
+
<div className="hidden md:block text-left">
|
|
106
|
+
<p className="text-sm font-medium text-gray-900">{displayName}</p>
|
|
107
|
+
<p className="text-xs text-gray-500">{displayRole}</p>
|
|
108
|
+
</div>
|
|
109
|
+
<ChevronDown className="h-4 w-4 text-gray-400" />
|
|
110
|
+
</Button>
|
|
111
|
+
</DropdownMenuTrigger>
|
|
112
|
+
<DropdownMenuContent align="end" className="w-56">
|
|
113
|
+
<DropdownMenuLabel>
|
|
114
|
+
<div>
|
|
115
|
+
<p className="font-medium">{displayName}</p>
|
|
116
|
+
<p className="text-xs text-gray-500">{displayEmail}</p>
|
|
117
|
+
</div>
|
|
118
|
+
</DropdownMenuLabel>
|
|
119
|
+
<DropdownMenuSeparator />
|
|
120
|
+
<DropdownMenuItem>
|
|
121
|
+
<User className="mr-2 h-4 w-4" />
|
|
122
|
+
Profile
|
|
123
|
+
</DropdownMenuItem>
|
|
124
|
+
<DropdownMenuItem>
|
|
125
|
+
<Settings className="mr-2 h-4 w-4" />
|
|
126
|
+
Settings
|
|
127
|
+
</DropdownMenuItem>
|
|
128
|
+
<DropdownMenuSeparator />
|
|
129
|
+
<DropdownMenuItem className="text-red-600">
|
|
130
|
+
<LogOut className="mr-2 h-4 w-4" />
|
|
131
|
+
Sign out
|
|
132
|
+
</DropdownMenuItem>
|
|
133
|
+
</DropdownMenuContent>
|
|
134
|
+
</DropdownMenu>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</header>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
import Sidebar from './sidebar'
|
|
3
|
+
import DashboardHeader from './dashboard-header'
|
|
4
|
+
|
|
5
|
+
interface DashboardLayoutProps {
|
|
6
|
+
children: ReactNode
|
|
7
|
+
title?: string
|
|
8
|
+
subtitle?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function DashboardLayout({ children, title, subtitle }: DashboardLayoutProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="flex h-screen bg-gray-50">
|
|
14
|
+
{/* Sidebar */}
|
|
15
|
+
<Sidebar />
|
|
16
|
+
|
|
17
|
+
{/* Main content area */}
|
|
18
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
19
|
+
{/* Header */}
|
|
20
|
+
<DashboardHeader title={title} subtitle={subtitle} />
|
|
21
|
+
|
|
22
|
+
{/* Main content */}
|
|
23
|
+
<main className="flex-1 overflow-y-auto p-6">
|
|
24
|
+
{children}
|
|
25
|
+
</main>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Link, useLocation } from "react-router-dom";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { Home, LayoutDashboard } from "lucide-react";
|
|
4
|
+
import { BluecopaLogo } from "../ui/bluecopa-logo";
|
|
5
|
+
|
|
6
|
+
const navigation = [
|
|
7
|
+
{ name: "Home", href: "/", icon: Home },
|
|
8
|
+
{ name: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export default function Sidebar() {
|
|
12
|
+
const location = useLocation();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex h-full w-64 flex-col bg-white border-r border-gray-200">
|
|
16
|
+
{/* Logo */}
|
|
17
|
+
<div className="flex h-16 items-center px-6 border-b border-gray-200">
|
|
18
|
+
<Link to="/" className="flex items-center space-x-2">
|
|
19
|
+
<BluecopaLogo className="h-6 w-6 text-white" />
|
|
20
|
+
<span className="text-xl font-bold text-gray-900">Bluecopa</span>
|
|
21
|
+
</Link>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
{/* Navigation */}
|
|
25
|
+
<nav className="flex-1 space-y-1 px-3 py-4">
|
|
26
|
+
{navigation.map((item) => {
|
|
27
|
+
const isActive = location.pathname === item.href;
|
|
28
|
+
return (
|
|
29
|
+
<Link
|
|
30
|
+
key={item.name}
|
|
31
|
+
to={item.href}
|
|
32
|
+
className={cn(
|
|
33
|
+
"group flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors",
|
|
34
|
+
isActive
|
|
35
|
+
? "bg-blue-50 text-blue-700 border-r-2 border-blue-600"
|
|
36
|
+
: "text-gray-700 hover:bg-gray-50 hover:text-gray-900"
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<item.icon
|
|
40
|
+
className={cn(
|
|
41
|
+
"mr-3 h-5 w-5 flex-shrink-0",
|
|
42
|
+
isActive
|
|
43
|
+
? "text-blue-600"
|
|
44
|
+
: "text-gray-400 group-hover:text-gray-500"
|
|
45
|
+
)}
|
|
46
|
+
/>
|
|
47
|
+
{item.name}
|
|
48
|
+
</Link>
|
|
49
|
+
);
|
|
50
|
+
})}
|
|
51
|
+
</nav>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|