diff --git a/public/categories/sports.jpg b/public/categories/sports.jpg new file mode 100644 index 0000000..a44acb8 Binary files /dev/null and b/public/categories/sports.jpg differ diff --git a/public/images/logo-high.png b/public/images/logo-high.png new file mode 100644 index 0000000..49194d9 Binary files /dev/null and b/public/images/logo-high.png differ diff --git a/public/images/logo-low.png b/public/images/logo-low.png new file mode 100644 index 0000000..42a7c20 Binary files /dev/null and b/public/images/logo-low.png differ diff --git a/public/logo-1.png b/public/logo-1.png deleted file mode 100644 index 6d372c5..0000000 Binary files a/public/logo-1.png and /dev/null differ diff --git a/public/logo-2.png b/public/logo-2.png deleted file mode 100644 index 90d3d7e..0000000 Binary files a/public/logo-2.png and /dev/null differ diff --git a/src/app/(home-app)/_components/CategorySection.tsx b/src/app/(home-app)/_components/CategorySection.tsx index 5a72f5e..b930d4b 100644 --- a/src/app/(home-app)/_components/CategorySection.tsx +++ b/src/app/(home-app)/_components/CategorySection.tsx @@ -13,28 +13,18 @@ import { } from "lucide-react"; import { useRouter } from "next/navigation"; -// --- Enhanced CATEGORY_CONFIG to better match the visual style --- -// I've added more icons to demonstrate flexibility. -// You can map your actual category names to these. +// --- Enhanced CATEGORY_CONFIG with modern color scheme --- +// Using primary and chart colors from the theme for consistency export const CATEGORY_CONFIG: Record = { - // Keep your existing ones - "Workshops & Education": {icon: BookOpen, color: "text-blue-500"}, - "Music": {icon: Music, color: "text-purple-500"}, - "Arts & Theatre": {icon: Brush, color: "text-pink-500"}, - "Performing & Visual Arts": {icon: Brush, color: "text-pink-500"}, - "Food & Drink": {icon: Utensils, color: "text-amber-500"}, - "Sports & Fitness": {icon: Dumbbell, color: "text-green-500"}, - "Community & Social": {icon: Users, color: "text-teal-500"}, + "Workshops & Education": {icon: BookOpen, color: "text-primary"}, + "Music": {icon: Music, color: "text-chart-2"}, + "Arts & Theatre": {icon: Brush, color: "text-chart-3"}, + "Performing & Visual Arts": {icon: Brush, color: "text-chart-3"}, + "Food & Drink": {icon: Utensils, color: "text-chart-1"}, + "Sports & Fitness": {icon: Dumbbell, color: "text-chart-2"}, + "Community & Social": {icon: Users, color: "text-chart-1"}, }; -// --- Skeleton Component for a cleaner loading state --- -const CategorySkeleton = () => ( -
-
-
-
-); - export default function CategorySection() { const [categories, setCategories] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -67,9 +57,18 @@ export default function CategorySection() { return (
-
- {[...Array(8)].map((_, i) => ( - +
+ {[...Array(6)].map((_, i) => ( +
+
+
+
+
+ {/* Decorative elements */} +
+
+
+
))}
@@ -81,18 +80,27 @@ export default function CategorySection() { return (
-

{error}

+
+
+
+ + + + +
+

Oops! Something went wrong

+

{error}

+
+
); } - // Note: You might need a scrollbar hiding plugin for Tailwind if you want to hide it completely - // e.g., `tailwind-scrollbar-hide` and then add the `scrollbar-hide` class to the div below. return (
-
+
{categories.map((category) => { const config = CATEGORY_CONFIG[category.name] || { icon: BookOpen, // A sensible default @@ -100,22 +108,48 @@ export default function CategorySection() { }; const IconComponent = config.icon; + // Extract color without 'text-' prefix for background + const colorClass = config.color.replace('text-', ''); + + //Hide Other category + if (category.name === "Other") { + return null; + } + return (
handleCategoryClick(category.id)} > -
- +
+ {/* Main card */} +
+ {/* Subtle gradient background that changes based on category */} +
+ + {/* Icon with glowing effect on hover */} +
+
+
+ +
+
+
+ + {/* Decorative background element */} +
+
+ + {/* Category name with hover effect */} +
+

+ {category.name} +

-

- {category.name} -

); })} diff --git a/src/app/(home-app)/_components/Topbar.tsx b/src/app/(home-app)/_components/Topbar.tsx index 3fe14c5..f046fb3 100644 --- a/src/app/(home-app)/_components/Topbar.tsx +++ b/src/app/(home-app)/_components/Topbar.tsx @@ -1,8 +1,9 @@ 'use client' import Link from 'next/link' +import Image from 'next/image' import {useAuth} from '@/providers/AuthProvider' -import {Ticket, ShieldCheck, Receipt} from 'lucide-react' +import {ShieldCheck, Receipt} from 'lucide-react' import {Button} from '@/components/ui/button' import { DropdownMenu, @@ -26,9 +27,17 @@ export default function Topbar() {
-
- - Ticketly +
+
+ Ticketly Logo +
+ Ticketly
diff --git a/src/app/(home-app)/events/[event_id]/[session_id]/_components/SelectionSummery.tsx b/src/app/(home-app)/events/[event_id]/[session_id]/_components/SelectionSummery.tsx index 75fef97..2dbb01b 100644 --- a/src/app/(home-app)/events/[event_id]/[session_id]/_components/SelectionSummery.tsx +++ b/src/app/(home-app)/events/[event_id]/[session_id]/_components/SelectionSummery.tsx @@ -58,40 +58,47 @@ export const SelectionSummary = ({ useEffect(() => { if (!initialDiscountCode) return; - if (initialDiscountCode) { - const params = new URLSearchParams(window.location.search); - params.delete('discount'); - const newUrl = `${window.location.pathname}?${params.toString()}`; - window.history.replaceState({}, '', newUrl); - } + // Remove ?discount= from the URL + const params = new URLSearchParams(window.location.search); + params.delete('discount'); + const newUrl = `${window.location.pathname}?${params.toString()}`; + window.history.replaceState({}, '', newUrl); const urlDiscountCode = initialDiscountCode; - if (!appliedDiscount && publicDiscounts.length > 0) { - const discount = publicDiscounts.find( - d => d.code.toUpperCase() === urlDiscountCode.toUpperCase() - ); - - if (discount) { - handleApplyDiscount(discount); - } else { - (async () => { - try { - const result = await getDiscountByCode(eventId, sessionId, urlDiscountCode); - if (result) { - handleApplyDiscount(result); - } else { - console.log("Discount code in URL is not valid:", urlDiscountCode); + // Only apply if no discount already applied + if (!appliedDiscount) { + const applyDiscount = async () => { + try { + // If discounts are preloaded (public), check them first + if (publicDiscounts.length > 0) { + const discount = publicDiscounts.find( + d => d.code.toUpperCase() === urlDiscountCode.toUpperCase() + ); + if (discount) { + handleApplyDiscount(discount); + return; } - } catch (e) { - console.error("Error validating discount code:", e); } - })(); - } + + // Otherwise, fetch discount from API + const result = await getDiscountByCode(eventId, sessionId, urlDiscountCode); + if (result) { + handleApplyDiscount(result); + } else { + console.log("Discount code in URL is not valid:", urlDiscountCode); + } + } catch (e) { + console.error("Error validating discount code:", e); + } + }; + + applyDiscount(); } }, [publicDiscounts, initialDiscountCode, appliedDiscount, eventId, sessionId, handleApplyDiscount]); + // Validate applied discount when cart changes useEffect(() => { if (appliedDiscount) { diff --git a/src/app/(home-app)/events/[event_id]/_components/NetflixHero.tsx b/src/app/(home-app)/events/[event_id]/_components/NetflixHero.tsx new file mode 100644 index 0000000..da6d7ff --- /dev/null +++ b/src/app/(home-app)/events/[event_id]/_components/NetflixHero.tsx @@ -0,0 +1,176 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import Image from 'next/image'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Eye, Tag } from 'lucide-react'; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { EventBasicInfoDTO } from "@/types/event"; +import { cn } from '@/lib/utils'; + +interface NetflixHeroProps { + event: EventBasicInfoDTO; + viewCount?: number; +} + +export function NetflixHero({ event, viewCount }: NetflixHeroProps) { + const [currentImageIndex, setCurrentImageIndex] = useState(0); + const { title, coverPhotos, organization, category } = event; + + // Default to at least one image + const images = coverPhotos && coverPhotos.length > 0 + ? coverPhotos + : ['/logo-1.png']; // fallback + + // Auto rotate images + useEffect(() => { + if (images.length <= 1) return; + const interval = setInterval(() => { + setCurrentImageIndex((prev) => (prev + 1) % images.length); + }, 7000); + return () => clearInterval(interval); + }, [images.length]); + + return ( +
+ {/* Background images with fade transition */} + + + {title} + {/* Dark cinematic overlays */} +
+
+ + + + {/* Foreground content */} +
+
+
+ {/* Glassy organization info */} + {organization && ( + + + + + {organization.name?.charAt(0).toUpperCase()} + + +
+ Presented by + {organization.name} +
+
+ )} + + {/* Title */} + + {title} + + + {/* Glassy event details */} + + {category && ( + + + {category.name} + {category.parentName && ( + ({category.parentName}) + )} + + )} + + {viewCount !== undefined && ( + + + {viewCount.toLocaleString()} views + + )} + + + {/* CTA button with strong glass effect */} + + + +
+
+
+ + {/* Image indicators (glassy dots) */} + {images.length > 1 && ( +
+ {images.map((_, index) => ( +
+ )} +
+ ); +} + +export function NetflixHeroSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/src/app/(home-app)/events/[event_id]/layout.tsx b/src/app/(home-app)/events/[event_id]/layout.tsx index 60626c5..3586e4d 100644 --- a/src/app/(home-app)/events/[event_id]/layout.tsx +++ b/src/app/(home-app)/events/[event_id]/layout.tsx @@ -1,9 +1,12 @@ import React, { Suspense } from 'react'; -import { EventHero, ReviewEventHeroSkeleton } from "@/app/(home-app)/events/[event_id]/_components/EventHero"; import { Separator } from "@/components/ui/separator"; import { getEventSummery, getEventTotalViews } from "@/lib/actions/public/server/eventActions"; import { EventOverview } from "@/app/manage/_components/review/EventOverview"; import { EventTracker } from './_components/EventTracker'; +import { NetflixHero, NetflixHeroSkeleton } from './_components/NetflixHero'; +import { Card, CardContent } from "@/components/ui/card"; +import TiersSection from "./_components/TiersSection"; +import { OffersCarousel } from "./_components/OffersCarousel"; export default async function Layout({ params, children }: { @@ -16,21 +19,62 @@ export default async function Layout({ params, children }: { return (
-
-
- }> - - - - - - - {children} + + + {/* Netflix-style Hero Section */} + }> + + + + {/* Content Section */} +
+
+ {/* Two column layout for description/overview and tickets */} +
+ {/* Left Column: Event Details */} +
+ {eventSummery.description && ( +
+

About this event

+

{eventSummery.description}

+
+ )} + + + + + + +
+ + {/* Right Column: Ticket Panel - Using sticky positioning */} +
+
+ + +

Tickets

+ {eventSummery.tiers && eventSummery.tiers.length > 0 && ( + + )} +
+
+ + +
+
+
+ + {/* Full-width content section */} +
+ {children} +
diff --git a/src/app/(home-app)/orders/page.tsx b/src/app/(home-app)/orders/page.tsx index 55896e5..8b32cbc 100644 --- a/src/app/(home-app)/orders/page.tsx +++ b/src/app/(home-app)/orders/page.tsx @@ -114,9 +114,9 @@ export default function OrdersPage() { const getStatusVariant = (status: string) => { switch (status.toLowerCase()) { case 'completed': - return 'default'; + return 'success'; case 'pending': - return 'secondary'; + return 'warning'; case 'cancelled': return 'destructive'; case 'expired': @@ -126,21 +126,6 @@ export default function OrdersPage() { } }; - const getStatusColor = (status: string) => { - switch (status.toLowerCase()) { - case 'completed': - return 'text-white'; - case 'pending': - return 'text-amber-500'; - case 'cancelled': - return 'text-red-500'; - case 'expired': - return 'text-gray-500'; - default: - return 'text-gray-500'; - } - }; - return (
@@ -173,7 +158,7 @@ export default function OrdersPage() {
Order #{order.OrderID.slice(-8)} - + {order.Status} @@ -279,7 +264,7 @@ export default function OrdersPage() {
Order ID: {order.OrderID}
Status: - + {order.Status}
diff --git a/src/app/(home-app)/page.tsx b/src/app/(home-app)/page.tsx index 3e0d499..045cad5 100644 --- a/src/app/(home-app)/page.tsx +++ b/src/app/(home-app)/page.tsx @@ -7,6 +7,7 @@ import {sriLankaLocations} from "@/app/(home-app)/_utils/locations"; import {LocationCard} from "@/app/(home-app)/_components/LocationCard"; import {ArrowRight, Calendar, MapPin, Ticket} from "lucide-react"; import Link from 'next/link'; +import Image from 'next/image'; import {useAuth} from '@/providers/AuthProvider'; import { useEffect, useState } from 'react'; import {getTrendingEvents, getTotalSessionsCount, getTotalTicketsSold} from '@/lib/actions/public/eventActions'; @@ -72,144 +73,219 @@ export default function HomePage() { return (
{/* Hero Section */} -
-
+
+ {/* Abstract background shapes */} +
+
+
+
+
+
+
+ +
-

- Discover - Amazing Events +
+ The premier ticketing platform +
+ +

+ Discover Amazing Events

+

Find and book extraordinary experiences happening around you

{/* Stats */}
-
-
- -
-
- {loadingSessionsCount ? ( - - ) : ( - <> +
+
+
+
+ +
+
+ {loadingSessionsCount ? ( + + ) : ( - - )} + )} +
+
Events
-
Events
-
-
- + +
+
+
+
+ +
+
25+
+
Cities
-
25+
-
Cities
-
-
- -
-
- {loadingTicketsCount ? ( - - ) : ( - <> + +
+
+
+
+ +
+
+ {loadingTicketsCount ? ( + + ) : ( - - )} + )} +
+
Tickets Sold
-
Tickets Sold
- - - +
+ + + + + + +
{/* Categories Section */} -
-
-
-

- Browse by Category -

-

- Discover events tailored to your interests across various categories -

+
+
+
+
+ +
+
+
+
+ CATEGORIES +
+

+ Browse by Category +

+

+ Discover events tailored to your interests across various categories. From music concerts to workshops, find events that match your passion. +

+
+
+ +
+
-
{/* Trending Events Section */} -
-
-
-

- Trending Events -

-

- Don't miss out on the most popular events happening right now -

+
+
+ + {/* Decorative elements */} +
+
+
+ +
+
+
+
+ HOT & HAPPENING +
+

+ Trending Events + +

+

+ Don't miss out on the most popular events happening right now. These are the events everyone is talking about. +

+
+ + + +
{error && ( -
-

{error}

- +
+
+
+

Oops! Something went wrong

+

{error}

+ +
)}
{loading ? ( - // Skeleton loading UI + // Enhanced skeleton loading UI with animation Array(3).fill(0).map((_, index) => ( -
- - - - - +
+
+ +
+ +
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
)) ) : trendingEvents.length > 0 ? ( @@ -219,16 +295,20 @@ export default function HomePage() { )) ) : ( // No events found -
-

No trending events found at the moment.

+
+
+ +
+

No Events Found

+

We couldn't find any trending events at the moment. Check back later!

)}
- {/* View All Link */} -
+ {/* Mobile View All Link */} +
- @@ -238,55 +318,128 @@ export default function HomePage() {
{/* Locations Section */} -
-
-
-

- Explore by Location -

-

- Find exciting events happening in cities across Sri Lanka -

+
+
+ + {/* Map-like decorative elements */} +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ DESTINATIONS +
+

+ Explore by Location +

+

+ Find exciting events happening in cities across Sri Lanka. Discover local culture, music, and entertainment wherever you go. +

+
+ +
+
+
+
+ +
+
+
-
+ +
{sriLankaLocations .filter(location => !!location.imageUrl) .map((location) => ( ))}
+ +
+
+ + More locations coming soon +
+
{/* CTA Section */} -
-
-

- Ready to discover your next adventure? -

-

- {isAuthenticated - ? "Create amazing events and reach thousands of attendees" - : "Join thousands of people who trust us to find their perfect events" - } -

- - {isAuthenticated ? ( - - - - ) : ( - - )} +
+
+ {/* Abstract shapes */} +
+
+
+
+ + {/* Decorative circles */} +
+
+
+
+ +
+
+
+
+
+

+ Ready to discover your next adventure? +

+

+ {isAuthenticated + ? "Create amazing events and reach thousands of attendees with our powerful platform." + : "Join thousands of people who trust us to find their perfect events and experiences." + } +

+ + {isAuthenticated ? ( + + + + ) : ( + + )} +
+ +
+
+
+
+
+
+ Ticketly Logo +
+
+
+
+
+
+
diff --git a/src/app/globals.css b/src/app/globals.css index a022398..870a38e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -349,4 +349,62 @@ .prose strong { color: hsl(var(--foreground)); font-weight: 600; +} + +/* Netflix Hero Styles */ +.netflix-vignette { + position: relative; +} + +.netflix-vignette::after { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at center, transparent 0%, rgba(0, 0, 0, 0.4) 100%); + pointer-events: none; + z-index: 5; +} + +.netflix-title-reveal { + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8), 0 4px 15px rgba(0, 0, 0, 0.5); + letter-spacing: -0.02em; + line-height: 1.1; +} + +/* Improve text contrast in the Netflix hero section */ +.netflix-vignette h1, +.netflix-vignette .text-xs, +.netflix-vignette .font-medium, +.netflix-vignette .badge, +.netflix-vignette button, +.netflix-vignette span { + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8); + color: white !important; /* Force white color regardless of theme */ +} + +/* Ensure all icons in the Netflix hero are white */ +.netflix-vignette svg { + color: white !important; +} + +/* Home page animations and utilities */ +@keyframes gradient-shift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.animate-gradient { + background-size: 200% 200%; + animation: gradient-shift 8s ease infinite; +} + +@keyframes spin-slow { + to { + transform: rotate(360deg); + } +} + +.animate-spin-slow { + animation: spin-slow 20s linear infinite; } \ No newline at end of file diff --git a/src/app/manage/organization/[organization_id]/event/[eventId]/discounts/_components/DiscountUsageChart.tsx b/src/app/manage/organization/[organization_id]/event/[eventId]/discounts/_components/DiscountUsageChart.tsx index 031d5c5..74aab5d 100644 --- a/src/app/manage/organization/[organization_id]/event/[eventId]/discounts/_components/DiscountUsageChart.tsx +++ b/src/app/manage/organization/[organization_id]/event/[eventId]/discounts/_components/DiscountUsageChart.tsx @@ -31,32 +31,33 @@ export const DiscountUsageChart: React.FC = ({ data }) const [pieMode, setPieMode] = useState<"amount" | "usage">("amount"); // Chart colors from globals.css - const chartColors = [ - "var(--color-chart-1)", // Teal - "var(--color-chart-2)", // Sky Blue - "var(--color-chart-3)", // Indigo - "var(--color-chart-4)", // Lime - "var(--color-chart-5)", // Orange - ]; + const chartColors = useMemo( + () => [ + "var(--color-chart-1)", // Teal + "var(--color-chart-2)", // Sky Blue + "var(--color-chart-3)", // Indigo + "var(--color-chart-4)", // Lime + "var(--color-chart-5)", // Orange + ], + [] + ); - // Group data by discount code - const discountsByCode = useMemo(() => { - return data.reduce((acc, item, index) => { - const code = item.discount_code; - if (!acc[code]) { - acc[code] = { - discount_code: code, - usage_count: 0, - total_discount_amount: 0, - // Use colors from globals.css with fallback - color: chartColors[Object.keys(acc).length % chartColors.length], - }; - } - acc[code].usage_count += item.usage_count; - acc[code].total_discount_amount += item.total_discount_amount; - return acc; - }, {} as Record); - }, [data]); + const discountsByCode = useMemo(() => { + return data.reduce((acc, item) => { + const code = item.discount_code; + if (!acc[code]) { + acc[code] = { + discount_code: code, + usage_count: 0, + total_discount_amount: 0, + color: chartColors[Object.keys(acc).length % chartColors.length], + }; + } + acc[code].usage_count += item.usage_count; + acc[code].total_discount_amount += item.total_discount_amount; + return acc; + }, {} as Record); + }, [chartColors, data]); // Create pie chart data sorted by amount or usage count const pieChartData = useMemo(() => { diff --git a/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/CustomSeatingLayout.tsx b/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/CustomSeatingLayout.tsx index 8f16ffc..a01f6f3 100644 --- a/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/CustomSeatingLayout.tsx +++ b/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/CustomSeatingLayout.tsx @@ -61,7 +61,7 @@ export const CustomSeatingLayout: React.FC = ({ session, tie return (

Seating Layout

-
+
{layoutData.layout.blocks.map(block => { // Get tier color for the block based on first seat's tier const firstSeatTierId = block.seats?.[0]?.tierId; diff --git a/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/SessionHeader.tsx b/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/SessionHeader.tsx index c0aa9de..d6b7367 100644 --- a/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/SessionHeader.tsx +++ b/src/app/manage/organization/[organization_id]/event/[eventId]/sessions/[sessionId]/_components/SessionHeader.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { format } from 'date-fns'; -import { LinkIcon, MapPin, Share2, Trash2, ChevronLeft } from 'lucide-react'; +import { LinkIcon, MapPin, Share2, Trash2 } from 'lucide-react'; import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { useParams } from 'next/navigation'; diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 0afae87..5abf43b 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -5,12 +5,12 @@ import { Building2, CalendarRange, RockingChair, - Ticket, LayoutDashboard, CalendarDays, Building, Users, } from "lucide-react" import Link from "next/link"; +import Image from "next/image"; import {useSidebar} from "@/components/ui/sidebar" import {NavMain} from "@/components/nav-main" import {NavOrg} from "@/components/nav-org" @@ -82,8 +82,16 @@ export function AppSidebar({...props}: React.ComponentProps) { asChild className={`data-[slot=sidebar-menu-button]:!p-2 ${!open ? 'justify-center' : ''}`} > - - + +
+ Ticketly Logo +
{open && Ticketly} @@ -158,7 +166,15 @@ export function AppSidebarAdmin({...props}: React.ComponentProps className={`data-[slot=sidebar-menu-button]:!p-2 ${!open ? 'justify-center' : ''}`} > - +
+ Ticketly Logo +
{open && (
Ticketly diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx index a3330be..3f89bea 100644 --- a/src/components/nav-main.tsx +++ b/src/components/nav-main.tsx @@ -35,7 +35,7 @@ export function NavMain({ }) { return ( - Platform + Navigation {items.map((item) => { const hasSubItems = item.items && item.items.length > 0;