Compare commits

..

6 Commits

Author SHA1 Message Date
6d475650da feat: enhance layout with mobile navigation and added themes var 2026-02-21 21:18:41 +03:00
77944930d0 login and signup 2026-02-21 20:00:45 +03:00
612bb9386b feat: add hero banner, live events list, and sports navigation components
- Implemented HeroBanner component for image carousel with navigation arrows and indicators.
- Created LiveEventsList component to display live events with odds and match details.
- Added SportsNav component for sport category navigation with icons.
- Introduced TopMatches component to showcase highlighted matches with odds.
- Updated InPlayHeader and QuickFilterBar for improved UI and functionality.
- Enhanced ReloadTicket and SearchEvent components for better user experience.
- Refactored SportsSidebar to include popular leagues and quick filter options.
- Added new sport-home layout to integrate various betting components.
2026-02-20 12:22:04 +03:00
6376713398 feat: Implement multiple pages including Check Ticket, Deposit, History, Live, Login, Profile, Promotions, Raffle, Register, and Rules with enhanced UI and functionality 2026-02-20 12:21:29 +03:00
cfb6b61120 feat: Add layout components including header, footer, sidebar, and tabs for improved navigation and user experience 2026-02-20 12:21:00 +03:00
86bd615b9e feat: Introduce initial sports betting application with core pages, UI components, and state management. 2026-02-20 12:20:07 +03:00
39 changed files with 3184 additions and 414 deletions

View File

@ -1,6 +1,69 @@
"use client"
import { useState } from "react"
export default function CheckTicketPage() { export default function CheckTicketPage() {
return ( const [ticketId, setTicketId] = useState("")
<div className="text-sm text-foreground">Check ticket page placeholder</div> const [result, setResult] = useState<null | object>(null)
)
const handleCheck = () => {
if (!ticketId.trim()) return
setResult({
id: ticketId,
event: "Arsenal vs Manchester City",
selection: "Arsenal Win",
odds: "2.80",
stake: "100 ETB",
potentialWin: "280 ETB",
status: "pending",
date: "2026-02-19 17:30",
})
} }
return (
<div className="max-w-md mx-auto mt-8 space-y-4">
<div className="border-b border-border pb-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">Check Ticket</h1>
</div>
<div className="bg-card border border-border rounded-lg p-6 space-y-4">
<div>
<label className="text-[11px] font-semibold text-muted-foreground uppercase block mb-2">Ticket ID / Bet Code</label>
<div className="flex gap-2">
<input
type="text"
value={ticketId}
onChange={(e) => setTicketId(e.target.value)}
placeholder="Enter ticket ID or bet code"
className="flex-1 bg-input border border-border rounded px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary"
onKeyDown={(e) => e.key === "Enter" && handleCheck()}
/>
<button
onClick={handleCheck}
className="bg-primary text-primary-foreground font-bold px-4 py-2 rounded uppercase text-sm hover:opacity-90 transition-opacity"
>
Check
</button>
</div>
</div>
{result && (
<div className="border border-border rounded overflow-hidden">
<div className="bg-secondary px-3 py-2 flex justify-between">
<span className="text-[11px] font-bold uppercase text-foreground">Ticket Details</span>
<span className="text-[10px] text-yellow-500 font-bold uppercase bg-yellow-500/20 px-2 py-0.5 rounded">Pending</span>
</div>
<div className="p-3 space-y-1.5 text-[11px]">
{Object.entries(result as Record<string, string>).filter(([k]) => k !== "status").map(([key, val]) => (
<div key={key} className="flex justify-between">
<span className="text-muted-foreground capitalize">{key.replace(/([A-Z])/g, ' $1')}:</span>
<span className="text-foreground font-medium">{val}</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
)
}

View File

@ -1,6 +1,94 @@
"use client"
import { useState } from "react"
const methods = [
{ id: "telebirr", name: "Telebirr", icon: "📱", desc: "Ethiopian mobile payment" },
{ id: "cbe", name: "CBE Birr", icon: "🏦", desc: "Commercial Bank of Ethiopia" },
{ id: "awash", name: "Awash Bank", icon: "🏛️", desc: "Awash Bank transfer" },
{ id: "abyssinia", name: "Bank of Abyssinia", icon: "💳", desc: "Bank of Abyssinia" },
]
const quickAmounts = [50, 100, 200, 500, 1000, 2000]
export default function DepositPage() { export default function DepositPage() {
const [method, setMethod] = useState("telebirr")
const [amount, setAmount] = useState("")
return ( return (
<div className="text-sm text-foreground">Deposit page placeholder</div> <div className="max-w-md mx-auto mt-8">
<div className="bg-card border border-border rounded-lg overflow-hidden">
<div className="bg-primary px-6 py-4">
<h1 className="text-base font-black text-primary-foreground uppercase tracking-wide">Deposit</h1>
<p className="text-[11px] text-primary-foreground/70 mt-0.5">Add funds to your account</p>
</div>
<div className="p-6 space-y-5">
{/* Balance */}
<div className="bg-secondary/50 border border-border rounded p-3 flex justify-between items-center">
<span className="text-[11px] text-muted-foreground uppercase font-semibold">Current Balance</span>
<span className="font-bold text-primary text-lg">0.00 ETB</span>
</div>
{/* Payment methods */}
<div>
<label className="text-[11px] font-semibold text-muted-foreground uppercase block mb-2">Payment Method</label>
<div className="grid grid-cols-2 gap-2">
{methods.map((m) => (
<button
key={m.id}
onClick={() => setMethod(m.id)}
className={`flex items-center gap-2 p-2.5 rounded border transition-all text-left ${
method === m.id
? "border-primary bg-primary/10 text-foreground"
: "border-border bg-secondary/30 text-muted-foreground hover:border-primary/50"
}`}
>
<span className="text-xl">{m.icon}</span>
<div>
<div className="text-[11px] font-semibold">{m.name}</div>
<div className="text-[10px] opacity-70">{m.desc}</div>
</div>
</button>
))}
</div>
</div>
{/* Amount */}
<div>
<label className="text-[11px] font-semibold text-muted-foreground uppercase block mb-2">Amount (ETB)</label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Enter amount"
className="w-full bg-input border border-border rounded px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary"
min="50"
/>
<div className="flex flex-wrap gap-1.5 mt-2">
{quickAmounts.map((a) => (
<button
key={a}
onClick={() => setAmount(String(a))}
className={`text-[11px] px-2.5 py-1 rounded border transition-colors font-semibold ${
amount === String(a)
? "bg-primary text-primary-foreground border-primary"
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
}`}
>
{a} ETB
</button>
))}
</div>
</div>
<p className="text-[10px] text-muted-foreground">Min deposit: 50 ETB · Max deposit: 50,000 ETB</p>
<button className="w-full bg-primary text-primary-foreground font-bold py-3 rounded uppercase text-sm hover:opacity-90 transition-opacity">
Deposit {amount ? `${amount} ETB` : ""}
</button>
</div>
</div>
</div>
) )
} }

View File

@ -42,85 +42,183 @@
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px); --radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px); /* Brand Theme Parameters */
--radius-4xl: calc(var(--radius) + 16px); --color-brand-primary: var(--brand-primary);
--color-brand-primary-hover: var(--brand-primary-hover);
--color-brand-secondary: var(--brand-secondary);
--color-brand-bg: var(--brand-bg);
--color-brand-surface: var(--brand-surface);
--color-brand-surface-light: var(--brand-surface-light);
--color-brand-accent: var(--brand-accent);
--color-brand-live: var(--brand-live);
} }
:root { :root {
--radius: 0.625rem; --radius: 0rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823); /* Brand Colors (Black, Yellow, White theme) */
--card: oklch(1 0 0); --brand-primary: #ff9800;
--card-foreground: oklch(0.141 0.005 285.823); --brand-primary-hover: #e68900;
--popover: oklch(1 0 0); --brand-secondary: #ff9800;
--popover-foreground: oklch(0.141 0.005 285.823); /* Usually same as primary for now */
--primary: oklch(0.21 0.006 285.885); --brand-bg: #121212;
--primary-foreground: oklch(0.985 0 0); --brand-surface: #1a1a1a;
--secondary: oklch(0.967 0.001 286.375); --brand-surface-light: #2a2a2a;
--secondary-foreground: oklch(0.21 0.006 285.885); --brand-accent: #852222;
--muted: oklch(0.967 0.001 286.375); /* Maroon */
--muted-foreground: oklch(0.552 0.016 285.938); --brand-live: #ff3b3b;
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885); /* Shadcn default mappings */
--destructive: oklch(0.577 0.245 27.325); --background: var(--brand-bg);
--border: oklch(0.92 0.004 286.32); --foreground: #ffffff;
--input: oklch(0.92 0.004 286.32); --card: #1e1e1e;
--ring: oklch(0.705 0.015 286.067); --card-foreground: #ffffff;
--chart-1: oklch(0.646 0.222 41.116); --popover: #1e1e1e;
--chart-2: oklch(0.6 0.118 184.704); --popover-foreground: #ffffff;
--chart-3: oklch(0.398 0.07 227.392); --primary: var(--brand-primary);
--chart-4: oklch(0.828 0.189 84.429); --primary-foreground: #ffffff;
--chart-5: oklch(0.769 0.188 70.08); --secondary: #222222;
--sidebar: oklch(0.985 0 0); --secondary-foreground: #a0a0a0;
--sidebar-foreground: oklch(0.141 0.005 285.823); --muted: #1a1a1a;
--sidebar-primary: oklch(0.21 0.006 285.885); --muted-foreground: #808080;
--sidebar-primary-foreground: oklch(0.985 0 0); --accent: var(--brand-primary);
--sidebar-accent: oklch(0.967 0.001 286.375); --accent-foreground: #121212;
--sidebar-accent-foreground: oklch(0.21 0.006 285.885); --destructive: #ef4444;
--sidebar-border: oklch(0.92 0.004 286.32); --border: #2a2a2a;
--sidebar-ring: oklch(0.705 0.015 286.067); --input: #222222;
--ring: var(--brand-primary);
} }
.dark { .dark {
--background: oklch(0.141 0.005 285.823); --background: oklch(0.13 0.008 250);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.93 0.005 250);
--card: oklch(0.21 0.006 285.885); --card: oklch(0.17 0.01 250);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.93 0.005 250);
--popover: oklch(0.21 0.006 285.885); --popover: oklch(0.17 0.01 250);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.93 0.005 250);
--primary: oklch(0.92 0.004 286.32); --primary: oklch(0.55 0.18 145);
--primary-foreground: oklch(0.21 0.006 285.885); --primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.274 0.006 286.033); --secondary: oklch(0.22 0.01 250);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.85 0.005 250);
--muted: oklch(0.274 0.006 286.033); --muted: oklch(0.2 0.008 250);
--muted-foreground: oklch(0.705 0.015 286.067); --muted-foreground: oklch(0.58 0.01 250);
--accent: oklch(0.274 0.006 286.033); --accent: oklch(0.55 0.18 145);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.98 0 0);
--destructive: oklch(0.704 0.191 22.216); --destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%); --border: oklch(0.25 0.01 250);
--input: oklch(1 0 0 / 15%); --input: oklch(0.22 0.01 250);
--ring: oklch(0.552 0.016 285.938); --ring: oklch(0.55 0.18 145);
--chart-1: oklch(0.488 0.243 264.376); --sidebar: oklch(0.15 0.01 250);
--chart-2: oklch(0.696 0.17 162.48); --sidebar-foreground: oklch(0.9 0.005 250);
--chart-3: oklch(0.769 0.188 70.08); --sidebar-primary: oklch(0.55 0.18 145);
--chart-4: oklch(0.627 0.265 303.9); --sidebar-primary-foreground: oklch(0.98 0 0);
--chart-5: oklch(0.645 0.246 16.439); --sidebar-accent: oklch(0.22 0.01 250);
--sidebar: oklch(0.21 0.006 285.885); --sidebar-accent-foreground: oklch(0.93 0.005 250);
--sidebar-foreground: oklch(0.985 0 0); --sidebar-border: oklch(0.25 0.01 250);
--sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-ring: oklch(0.55 0.18 145);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.552 0.016 285.938);
} }
@layer base { @layer base {
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-size: 13px;
} }
} }
/* HarifSport odds button animation */
@keyframes odds-flash {
0% {
background-color: oklch(0.55 0.18 145);
}
50% {
background-color: oklch(0.7 0.22 80);
}
100% {
background-color: oklch(0.55 0.18 145);
}
}
.odds-selected {
animation: odds-flash 0.4s ease;
}
/* Live pulse */
@keyframes live-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
.live-dot {
animation: live-pulse 1.2s ease-in-out infinite;
}
/* Auth modal fade-in */
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(-10px) scale(0.98);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.animate-fade-in {
animation: fade-in 0.18s ease-out both;
}
/* Mobile drawer slide-in from left */
@keyframes slide-in-left {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.animate-slide-in-left {
animation: slide-in-left 0.22s ease-out both;
}
/* Hide scrollbar for mobile nav */
.scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-none::-webkit-scrollbar {
display: none;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-track {
background: var(--background);
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 2px;
}

View File

@ -1,6 +1,65 @@
export default function HistoryPage() { const mockHistory = [
return ( { id: "BET001", event: "Arsenal vs Man City", selection: "Arsenal Win", odds: 2.80, stake: 100, status: "won", date: "2026-02-18" },
<div className="text-sm text-foreground">History page placeholder</div> { id: "BET002", event: "Bayern vs Dortmund", selection: "Over 2.5 Goals", odds: 1.65, stake: 50, status: "lost", date: "2026-02-17" },
) { id: "BET003", event: "NBA: Lakers vs Celtics", selection: "Lakers ML", odds: 1.85, stake: 200, status: "pending", date: "2026-02-19" },
{ id: "BET004", event: "St. George vs Dire Dawa", selection: "St. George Win", odds: 1.65, stake: 500, status: "won", date: "2026-02-16" },
]
const statusStyles: Record<string, string> = {
won: "text-primary bg-primary/20",
lost: "text-destructive bg-destructive/20",
pending: "text-yellow-500 bg-yellow-500/20",
} }
export default function HistoryPage() {
return (
<div className="space-y-4">
<div className="border-b border-border pb-2 flex items-center justify-between">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">Bet History</h1>
<span className="text-[11px] text-muted-foreground">Please login to see full history</span>
</div>
{/* Filter */}
<div className="flex gap-2">
{["All", "Won", "Lost", "Pending"].map((f) => (
<button key={f} className="px-3 py-1 text-[11px] font-semibold rounded border border-border text-muted-foreground hover:border-primary hover:text-primary transition-colors">
{f}
</button>
))}
</div>
<div className="bg-card border border-border rounded overflow-hidden">
<table className="w-full text-[11px]">
<thead>
<tr className="bg-secondary border-b border-border text-muted-foreground uppercase text-[10px]">
<th className="px-3 py-2 text-left font-semibold">Bet ID</th>
<th className="px-3 py-2 text-left font-semibold">Event</th>
<th className="px-3 py-2 text-left font-semibold">Selection</th>
<th className="px-3 py-2 text-right font-semibold">Odds</th>
<th className="px-3 py-2 text-right font-semibold">Stake</th>
<th className="px-3 py-2 text-center font-semibold">Status</th>
<th className="px-3 py-2 text-left font-semibold">Date</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{mockHistory.map((bet) => (
<tr key={bet.id} className="hover:bg-muted/30 transition-colors">
<td className="px-3 py-2 font-mono text-muted-foreground">{bet.id}</td>
<td className="px-3 py-2 font-medium text-foreground">{bet.event}</td>
<td className="px-3 py-2 text-muted-foreground">{bet.selection}</td>
<td className="px-3 py-2 text-right font-bold text-primary">{bet.odds.toFixed(2)}</td>
<td className="px-3 py-2 text-right">{bet.stake} ETB</td>
<td className="px-3 py-2 text-center">
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase ${statusStyles[bet.status]}`}>
{bet.status}
</span>
</td>
<td className="px-3 py-2 text-muted-foreground">{bet.date}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}

View File

@ -1,9 +1,7 @@
import type { Metadata } from "next" import type { Metadata } from "next"
import { Geist, Geist_Mono } from "next/font/google" import { Geist, Geist_Mono } from "next/font/google"
import "./globals.css" import "./globals.css"
import { SiteHeader } from "@/components/layout/site-header" import LayoutClientWrapper from "@/components/layout/layout-client-wrapper"
import { SportsSidebar } from "@/components/layout/sports-sidebar"
import { RightPanel } from "@/components/layout/right-panel"
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: "--font-geist-sans",
@ -16,8 +14,8 @@ const geistMono = Geist_Mono({
}) })
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Harifsport", title: "Harifsport - Sports Betting",
description: "Harifsport-style sportsbook interface built with Next.js", description: "Harifsport sportsbook - Live betting, in-play events, and more",
} }
export default function RootLayout({ export default function RootLayout({
@ -30,16 +28,8 @@ export default function RootLayout({
<body <body
className={`${geistSans.variable} ${geistMono.variable} bg-background text-foreground antialiased`} className={`${geistSans.variable} ${geistMono.variable} bg-background text-foreground antialiased`}
> >
<div className="flex min-h-screen flex-col bg-background"> <LayoutClientWrapper>{children}</LayoutClientWrapper>
<SiteHeader />
<div className="mx-auto flex w-full max-w-6xl flex-1 gap-0 px-2 py-3 lg:px-4">
<SportsSidebar />
<main className="flex-1 px-1 lg:px-3">{children}</main>
<RightPanel />
</div>
</div>
</body> </body>
</html> </html>
) )
} }

View File

@ -1,6 +1,9 @@
import { InPlayPage } from "@/components/betting/in-play-page" import { LiveEventsList } from "@/components/betting/live-events-list"
export default function LivePage() { export default function LivePage() {
return <InPlayPage /> return (
<div className="space-y-4">
<LiveEventsList />
</div>
)
} }

View File

@ -1,4 +1,131 @@
export default function LoginPage() { "use client"
return <div className="text-sm text-foreground">Login page placeholder</div>
}
import { useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
export default function LoginPage() {
const router = useRouter()
const [phone, setPhone] = useState("")
const [password, setPassword] = useState("")
return (
/* Full-screen dark backdrop */
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70"
onClick={() => router.back()}
>
{/* Modal card */}
<div
className="relative w-[420px] bg-[#3a3a3a] shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Close button */}
<button
onClick={() => router.back()}
className="absolute top-2 right-3 text-white/60 hover:text-white text-xl leading-none transition-colors z-10"
aria-label="Close"
>
×
</button>
{/* Logo */}
<div className="flex items-center justify-center py-5 border-b border-white/10 bg-[#2e2e2e]">
<div className="flex items-center">
{/* Red slashes */}
<div className="flex gap-[3px] mr-2">
{[0, 1, 2].map((i) => (
<div
key={i}
className="w-[5px] h-[38px] bg-[#cc2222] -skew-x-12"
/>
))}
</div>
{/* HARIF box */}
<div className="bg-[#852222] px-3 py-1 -skew-x-12 flex items-center h-[38px]">
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
HARIF
</span>
</div>
{/* SPORT text */}
<span className="text-2xl font-black text-[#ff9800] italic tracking-tighter ml-1 leading-none">
SPORT
</span>
</div>
</div>
{/* Title */}
<div className="text-center py-4">
<h1 className="text-sm font-black text-white uppercase tracking-widest">
LOGIN
</h1>
</div>
{/* Form */}
<div className="px-8 pb-6 space-y-4">
{/* Phone Number */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Phone Number
</label>
<div className="flex">
<span className="flex items-center justify-center bg-white text-[#333] text-[12px] font-bold px-3 border border-gray-300 whitespace-nowrap">
ET +251
</span>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="flex-1 bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
/>
</div>
</div>
{/* Password */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
/>
</div>
{/* Forgot password */}
<div className="text-right">
<Link
href="/reset-password"
className="text-[11px] text-white/50 hover:text-[#ff9800] transition-colors"
>
Forgot password?
</Link>
</div>
{/* Login button */}
<button className="w-full bg-[#e6b800] hover:bg-[#ffcc00] text-black font-black py-3 uppercase text-sm tracking-widest transition-colors">
LOGIN
</button>
{/* Support link */}
<p className="text-center text-[12px] text-white/60">
<Link href="/support" className="hover:text-white transition-colors">
Support
</Link>
</p>
{/* Register link */}
<p className="text-center text-[11px] text-white/50">
Don&apos;t have an account?{" "}
<Link href="/register" className="text-[#ff9800] hover:underline font-semibold">
Register
</Link>
</p>
</div>
</div>
</div>
)
}

View File

@ -1,6 +1,6 @@
import { InPlayPage } from "@/components/betting/in-play-page" import { SportHome } from "@/components/betting/sport-home"
export default function Home() { export default function Home() {
return <InPlayPage /> return <SportHome />
} }

View File

@ -1,6 +1,50 @@
import Link from "next/link"
export default function ProfilePage() { export default function ProfilePage() {
return ( return (
<div className="text-sm text-foreground">Profile page placeholder</div> <div className="max-w-md mx-auto mt-8 space-y-4">
<div className="border-b border-border pb-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">My Profile</h1>
</div>
<div className="bg-card border border-border rounded-lg overflow-hidden">
{/* Avatar */}
<div className="bg-gradient-to-r from-primary/30 to-primary/10 px-6 py-6 flex items-center gap-4">
<div className="size-16 bg-primary/20 rounded-full flex items-center justify-center text-2xl border-2 border-primary/30">
👤
</div>
<div>
<div className="font-bold text-foreground">Guest User</div>
<div className="text-[11px] text-muted-foreground">Not logged in</div>
<Link href="/login" className="inline-block mt-2 bg-primary text-primary-foreground text-[11px] font-bold px-3 py-1 rounded hover:opacity-90">
Login
</Link>
</div>
</div>
{/* Menu items */}
<div className="divide-y divide-border">
{[
{ icon: "🏆", label: "My Bets", href: "/history" },
{ icon: "💰", label: "Deposit", href: "/deposit" },
{ icon: "🎁", label: "Bonus", href: "/bonus" },
{ icon: "🔔", label: "Notifications", href: "/notifications" },
{ icon: "📋", label: "Rules", href: "/rules" },
].map((item) => (
<Link
key={item.label}
href={item.href}
className="flex items-center justify-between px-4 py-3 hover:bg-muted transition-colors group"
>
<div className="flex items-center gap-3">
<span className="text-lg">{item.icon}</span>
<span className="text-[12px] font-medium text-foreground">{item.label}</span>
</div>
<span className="text-muted-foreground group-hover:text-primary transition-colors"></span>
</Link>
))}
</div>
</div>
</div>
) )
} }

View File

@ -1,6 +1,60 @@
const promotions = [
{
id: 1,
title: "Welcome Bonus",
description: "Get 100% up to 500 ETB on your first deposit",
badge: "NEW",
color: "from-primary/20 to-primary/5",
icon: "🎁",
},
{
id: 2,
title: "Accumulator Boost",
description: "Get up to 50% extra winnings on accumulators of 5+ selections",
badge: "POPULAR",
color: "from-yellow-500/20 to-yellow-500/5",
icon: "⚡",
},
{
id: 3,
title: "Live Bet Cashback",
description: "5% cashback on all losing live bets, credited every Monday",
badge: "",
color: "from-blue-500/20 to-blue-500/5",
icon: "🔄",
},
]
export default function PromotionsPage() { export default function PromotionsPage() {
return ( return (
<div className="text-sm text-foreground">Promotions page placeholder</div> <div className="space-y-4">
<div className="border-b border-border pb-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">Promotions</h1>
</div>
<div className="grid gap-3">
{promotions.map((promo) => (
<div
key={promo.id}
className={`bg-gradient-to-r ${promo.color} border border-border rounded-lg p-4 flex items-center gap-4`}
>
<div className="text-4xl">{promo.icon}</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-bold text-sm text-foreground">{promo.title}</h3>
{promo.badge && (
<span className="bg-primary text-primary-foreground text-[10px] font-bold px-1.5 py-0.5 rounded">
{promo.badge}
</span>
)}
</div>
<p className="text-[11px] text-muted-foreground">{promo.description}</p>
</div>
<button className="shrink-0 bg-primary text-primary-foreground text-[11px] font-bold px-4 py-2 rounded hover:opacity-90 transition-opacity uppercase">
Claim
</button>
</div>
))}
</div>
</div>
) )
} }

View File

@ -1,4 +1,58 @@
export default function RafflePage() { "use client"
return <div className="text-sm text-foreground">Raffle page placeholder</div>
}
import { useState } from "react"
export default function RafflePage() {
const [tickets, setTickets] = useState(0)
return (
<div className="max-w-lg mx-auto mt-8 space-y-4">
<div className="border-b border-border pb-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">Raffle</h1>
</div>
{/* Current raffle */}
<div className="bg-gradient-to-br from-primary/20 to-card border border-primary/30 rounded-xl p-6 text-center">
<div className="text-5xl mb-3">🎰</div>
<h2 className="text-lg font-black text-foreground mb-1">Weekend Mega Raffle</h2>
<p className="text-[11px] text-muted-foreground mb-4">Prize pool: 100,000 ETB · Draw: Saturday 20:00</p>
<div className="bg-black/20 rounded-lg p-4 mb-4">
<div className="text-[10px] text-muted-foreground mb-1">Your tickets</div>
<div className="text-3xl font-black text-primary">{tickets}</div>
</div>
<div className="text-[11px] text-muted-foreground mb-4">
Earn 1 raffle ticket for every 100 ETB wagered
</div>
<a href="/deposit" className="inline-block w-full bg-primary text-primary-foreground font-bold py-3 rounded uppercase hover:opacity-90 transition-opacity">
Get More Tickets
</a>
</div>
{/* Past winners */}
<div className="bg-card border border-border rounded-lg overflow-hidden">
<div className="bg-secondary px-4 py-2 text-[11px] font-bold uppercase text-muted-foreground">Past Winners</div>
<div className="divide-y divide-border">
{[
{ name: "John D.", prize: "50,000 ETB", date: "Feb 15" },
{ name: "Sarah M.", prize: "20,000 ETB", date: "Feb 8" },
{ name: "Abebe K.", prize: "30,000 ETB", date: "Feb 1" },
].map((winner, i) => (
<div key={i} className="flex items-center justify-between px-4 py-3">
<div className="flex items-center gap-3">
<span className="text-lg">🏆</span>
<div>
<div className="text-[12px] font-semibold text-foreground">{winner.name}</div>
<div className="text-[10px] text-muted-foreground">{winner.date}</div>
</div>
</div>
<span className="text-primary font-bold text-[12px]">{winner.prize}</span>
</div>
))}
</div>
</div>
</div>
)
}

View File

@ -1,6 +1,150 @@
"use client"
import { useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
export default function RegisterPage() { export default function RegisterPage() {
const router = useRouter()
const [phone, setPhone] = useState("")
const [password, setPassword] = useState("")
const [repeatPassword, setRepeatPassword] = useState("")
const [ageConfirmed, setAgeConfirmed] = useState(false)
return ( return (
<div className="text-sm text-foreground">Register page placeholder</div> /* Full-screen dark backdrop */
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70"
onClick={() => router.back()}
>
{/* Modal card — stop clicks propagating to backdrop */}
<div
className="relative w-[420px] bg-[#3a3a3a] shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
{/* Close button */}
<button
onClick={() => router.back()}
className="absolute top-2 right-3 text-white/60 hover:text-white text-xl leading-none transition-colors z-10"
aria-label="Close"
>
×
</button>
{/* Logo */}
<div className="flex items-center justify-center py-5 border-b border-white/10 bg-[#2e2e2e]">
<div className="flex items-center">
{/* Red slashes */}
<div className="flex gap-[3px] mr-2">
{[0, 1, 2].map((i) => (
<div
key={i}
className="w-[5px] h-[38px] bg-[#cc2222] -skew-x-12"
/>
))}
</div>
{/* HARIF box */}
<div className="bg-[#852222] px-3 py-1 -skew-x-12 flex items-center h-[38px]">
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
HARIF
</span>
</div>
{/* SPORT text */}
<span className="text-2xl font-black text-[#ff9800] italic tracking-tighter ml-1 leading-none">
SPORT
</span>
</div>
</div>
{/* Title */}
<div className="text-center py-4">
<h1 className="text-sm font-black text-white uppercase tracking-widest">
REGISTER
</h1>
</div>
{/* Form */}
<div className="px-8 pb-6 space-y-4">
{/* Phone Number */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Phone Number
</label>
<div className="flex">
<span className="flex items-center justify-center bg-white text-[#333] text-[12px] font-bold px-3 border border-gray-300 whitespace-nowrap">
ET +251
</span>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="flex-1 bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
/>
</div>
</div>
{/* Password */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
/>
</div>
{/* Repeat Password */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Repeat Password
</label>
<input
type="password"
value={repeatPassword}
onChange={(e) => setRepeatPassword(e.target.value)}
placeholder="Repeat Password"
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-[#ff9800]"
/>
</div>
{/* Age confirmation */}
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={ageConfirmed}
onChange={(e) => setAgeConfirmed(e.target.checked)}
className="w-4 h-4 accent-[#ff9800]"
/>
<span className="text-[12px] text-white/80">
I confirm I&apos;m over 21 years old
</span>
</label>
{/* Register button */}
<button className="w-full bg-[#e6b800] hover:bg-[#ffcc00] text-black font-black py-3 uppercase text-sm tracking-widest transition-colors">
REGISTER
</button>
{/* Support link */}
<p className="text-center text-[12px] text-white/60">
<Link href="/support" className="hover:text-white transition-colors">
Support
</Link>
</p>
{/* Login link */}
<p className="text-center text-[11px] text-white/50">
Already have an account?{" "}
<Link href="/login" className="text-[#ff9800] hover:underline font-semibold">
Login
</Link>
</p>
</div>
</div>
</div>
) )
} }

View File

@ -1,4 +1,48 @@
export default function RulesPage() { const rules = [
return <div className="text-sm text-foreground">Rules page placeholder</div> {
} title: "General Betting Rules",
content: "All bets are subject to Harifsport terms and conditions. By placing a bet, you agree to abide by these rules. The minimum bet amount is 5 ETB and the maximum payout is 500,000 ETB per bet.",
},
{
title: "Live Betting",
content: "Live bets are accepted subject to availability. Odds may change at any time during live events. Bets placed during network disruptions may be voided at management's discretion.",
},
{
title: "Void Bets",
content: "Bets may be voided in cases of obvious error in odds, match postponement/cancellation, or system malfunction. Voided bets are returned to the account at stake value.",
},
{
title: "Responsible Gambling",
content: "Harifsport is committed to responsible gambling. Users may set deposit limits, loss limits, or self-exclude at any time. Gambling should be entertaining, not a source of income.",
},
{
title: "Account Rules",
content: "Each person may hold only one account. Duplicate accounts will be closed. Users must be 18+ years of age. Accounts showing signs of bonus abuse may be restricted.",
},
]
export default function RulesPage() {
return (
<div className="max-w-2xl space-y-4">
<div className="border-b border-border pb-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">Betting Rules</h1>
</div>
<div className="space-y-3">
{rules.map((rule, i) => (
<div key={i} className="bg-card border border-border rounded-lg overflow-hidden">
<div className="bg-secondary px-4 py-2.5 flex items-center gap-2">
<span className="bg-primary text-primary-foreground size-5 rounded-full text-[10px] font-bold flex items-center justify-center shrink-0">
{i + 1}
</span>
<h3 className="text-[12px] font-bold text-foreground">{rule.title}</h3>
</div>
<div className="px-4 py-3 text-[11px] text-muted-foreground leading-relaxed">
{rule.content}
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -1,25 +1,29 @@
import { Separator } from "@/components/ui/separator" import { BarChart2, PrinterIcon, Trophy } from "lucide-react"
const services = ["Live Score", "Results", "Print Odds"] const services = [
{ label: "Live Score", icon: BarChart2 },
{ label: "Results", icon: Trophy },
{ label: "Print Odds", icon: PrinterIcon },
]
export function BetServices() { export function BetServices() {
return ( return (
<div className="flex items-center gap-3 rounded-md border bg-card/60 px-3 py-2 text-[11px]"> <div className="flex items-center gap-0 bg-secondary rounded border border-border overflow-hidden text-[11px]">
<span className="font-medium uppercase tracking-wide text-muted-foreground"> <span className="px-3 py-2 font-bold uppercase text-muted-foreground text-[10px] tracking-wider border-r border-border">
Bet Services Services
</span> </span>
<Separator orientation="vertical" className="h-4" /> {services.map((service, i) => {
<div className="flex flex-wrap gap-2"> const Icon = service.icon
{services.map((service) => ( return (
<button <button
key={service} key={service.label}
className="text-[11px] text-primary underline-offset-2 hover:underline" className={`flex items-center gap-1.5 px-3 py-2 text-muted-foreground hover:text-primary hover:bg-muted transition-colors ${i < services.length - 1 ? "border-r border-border" : ""}`}
> >
{service} <Icon className="size-3" />
{service.label}
</button> </button>
))} )
</div> })}
</div> </div>
) )
} }

View File

@ -1,64 +1,187 @@
import { useBetslipStore } from "@/lib/store/betslip-store"; "use client"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { useState } from "react"
import { useBetslipStore } from "@/lib/store/betslip-store"
import { X, ChevronDown, Trash2 } from "lucide-react"
import { cn } from "@/lib/utils"
const quickStakes = [5, 10, 20, 50, 100, 200]
export function Betslip() { export function Betslip() {
const { bets, removeBet, clearBets } = useBetslipStore(); const { bets, removeBet, clearBets, updateStake, getPotentialWin, getTotalOdds } = useBetslipStore()
const [activeTab, setActiveTab] = useState<"single" | "accumulator">("single")
const [globalStake, setGlobalStake] = useState("10")
const potentialWin = getPotentialWin()
const totalOdds = getTotalOdds()
return ( return (
<Card> <div className="bg-card rounded border border-border overflow-hidden">
<CardHeader className="flex flex-row items-center justify-between space-y-0"> {/* Header */}
<CardTitle className="text-sm font-semibold"> <div className="bg-primary px-3 py-2 flex items-center justify-between">
Betslip {bets.length} <div className="flex items-center gap-2">
</CardTitle> <span className="text-[11px] font-bold text-primary-foreground uppercase">Betslip</span>
{bets.length > 0 && (
<span className="bg-primary-foreground text-primary text-[10px] font-bold px-1.5 py-0.5 rounded-full min-w-[18px] text-center">
{bets.length}
</span>
)}
</div>
{bets.length > 0 && ( {bets.length > 0 && (
<button <button
onClick={clearBets} onClick={clearBets}
className="text-xs text-muted-foreground hover:underline" className="text-primary-foreground/70 hover:text-primary-foreground transition-colors"
> >
Clear all <Trash2 className="size-3.5" />
</button> </button>
)} )}
</CardHeader> </div>
<CardContent className="space-y-3 text-xs">
{/* Tabs */}
{bets.length > 1 && (
<div className="flex border-b border-border">
{(["single", "accumulator"] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveTab(tab)}
className={cn(
"flex-1 py-1.5 text-[10px] font-semibold uppercase transition-colors",
activeTab === tab
? "bg-muted text-foreground border-b-2 border-primary"
: "text-muted-foreground hover:text-foreground"
)}
>
{tab}
</button>
))}
</div>
)}
{/* Content */}
<div className="p-2 space-y-2">
{bets.length === 0 ? ( {bets.length === 0 ? (
<p className="text-muted-foreground"> <div className="py-6 text-center">
No bet has been selected. To select a bet, please click on the <div className="text-2xl mb-2">🎯</div>
respective odds. <p className="text-[11px] text-muted-foreground leading-relaxed">
No bets selected. Click on odds to add selections.
</p> </p>
</div>
) : ( ) : (
<>
{/* Bet items */}
<div className="space-y-2"> <div className="space-y-2">
{bets.map((bet) => ( {bets.map((bet) => (
<div <div key={bet.id} className="bg-secondary/50 rounded border border-border/50 p-2">
key={bet.id} <div className="flex items-start justify-between gap-2 mb-2">
className="rounded border bg-card/40 p-2 text-xs" <div className="flex-1 min-w-0">
> <div className="text-[11px] font-semibold text-foreground truncate">{bet.event}</div>
<div className="flex items-center justify-between"> <div className="text-[10px] text-muted-foreground truncate">{bet.league}</div>
<div> <div className="text-[10px] text-primary font-medium mt-0.5">
<div className="font-medium">{bet.event}</div> {bet.market}: <span className="text-foreground">{bet.selection}</span>
<div className="text-[10px] text-muted-foreground">
{bet.market} {bet.selection}
</div> </div>
</div> </div>
<div className="text-right"> <div className="flex items-center gap-1.5 shrink-0">
<div className="font-semibold">{bet.odds.toFixed(2)}</div> <span className="text-sm font-bold text-primary">{bet.odds.toFixed(2)}</span>
<button <button
onClick={() => removeBet(bet.id)} onClick={() => removeBet(bet.id)}
className="text-[10px] text-destructive hover:underline" className="text-muted-foreground hover:text-destructive transition-colors"
> >
Remove <X className="size-3.5" />
</button> </button>
</div> </div>
</div> </div>
{/* Stake input for single */}
{(activeTab === "single" || bets.length === 1) && (
<div>
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</div>
<div className="flex gap-1">
<input
type="number"
value={bet.stake ?? 10}
onChange={(e) => updateStake(bet.id, Number(e.target.value))}
className="flex-1 bg-input border border-border rounded px-2 py-1 text-xs text-foreground w-full min-w-0 focus:outline-none focus:border-primary"
min="1"
/>
</div> </div>
<div className="flex gap-1 mt-1 flex-wrap">
{quickStakes.map((s) => (
<button
key={s}
onClick={() => updateStake(bet.id, s)}
className={cn(
"text-[10px] px-1.5 py-0.5 rounded border transition-colors",
(bet.stake ?? 10) === s
? "bg-primary text-primary-foreground border-primary"
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
)}
>
{s}
</button>
))} ))}
<Button className="w-full text-xs" size="sm"> </div>
Place bet {/* Potential win */}
</Button> <div className="mt-1.5 flex justify-between text-[10px]">
<span className="text-muted-foreground">Potential win:</span>
<span className="text-primary font-semibold">
{((bet.stake ?? 10) * bet.odds).toFixed(2)} ETB
</span>
</div>
</div> </div>
)} )}
</CardContent> </div>
</Card> ))}
); </div>
}
{/* Accumulator stake section */}
{activeTab === "accumulator" && bets.length > 1 && (
<div className="bg-secondary/50 rounded border border-border/50 p-2 space-y-2">
<div className="flex justify-between text-[11px]">
<span className="text-muted-foreground">Total Odds:</span>
<span className="font-bold text-primary">{totalOdds.toFixed(2)}</span>
</div>
<div>
<div className="text-[10px] text-muted-foreground mb-1">Stake (ETB)</div>
<input
type="number"
value={globalStake}
onChange={(e) => setGlobalStake(e.target.value)}
className="w-full bg-input border border-border rounded px-2 py-1 text-xs text-foreground focus:outline-none focus:border-primary"
min="1"
/>
<div className="flex gap-1 mt-1 flex-wrap">
{quickStakes.map((s) => (
<button
key={s}
onClick={() => setGlobalStake(String(s))}
className={cn(
"text-[10px] px-1.5 py-0.5 rounded border transition-colors",
globalStake === String(s)
? "bg-primary text-primary-foreground border-primary"
: "border-border text-muted-foreground hover:border-primary hover:text-primary"
)}
>
{s}
</button>
))}
</div>
<div className="mt-1.5 flex justify-between text-[10px]">
<span className="text-muted-foreground">Potential win:</span>
<span className="text-primary font-semibold">
{(Number(globalStake) * totalOdds).toFixed(2)} ETB
</span>
</div>
</div>
</div>
)}
{/* Place bet button */}
<button className="w-full bg-primary text-primary-foreground text-[12px] font-bold py-2.5 rounded hover:opacity-90 active:scale-95 transition-all uppercase tracking-wide">
Place Bet
</button>
</>
)}
</div>
</div>
)
}

View File

@ -1,26 +1,50 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" "use client"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button" import { useState } from "react"
export function CheckYourBet() { export function CheckYourBet() {
return ( const [betId, setBetId] = useState("")
<Card> const [result, setResult] = useState<null | "pending" | "won" | "lost">(null)
<CardHeader>
<CardTitle className="text-sm">Check your bet</CardTitle> const handleCheck = () => {
</CardHeader> if (!betId.trim()) return
<CardContent className="space-y-2 text-xs"> // Mock result
<div className="text-[11px] text-muted-foreground">Your bet ID</div> const results = ["pending", "won", "lost"] as const
<div className="flex gap-2"> setResult(results[Math.floor(Math.random() * results.length)])
<Input
placeholder="Bet ID"
className="h-8 text-xs placeholder:text-[11px]"
/>
<Button size="sm" className="text-xs">
Check
</Button>
</div>
</CardContent>
</Card>
)
} }
return (
<div className="bg-transparent border-t border-border/20 pt-4 pb-2">
<div className="px-1 text-center space-y-3">
<h3 className="text-[12px] font-bold uppercase text-[#ff9800]">Check Your Bet</h3>
<p className="text-[11px] text-white">Your bet ID</p>
<div className="flex gap-1">
<input
type="text"
value={betId}
onChange={(e) => setBetId(e.target.value)}
className="flex-1 bg-[#121212] border border-border/40 px-2 py-2 text-[11px] text-white outline-none focus:border-primary"
onKeyDown={(e) => e.key === "Enter" && handleCheck()}
/>
<button
onClick={handleCheck}
className="bg-[#ff9800] text-black px-2 py-1.5 flex items-center justify-center min-w-[32px] hover:bg-[#ffa726] transition-colors"
>
<svg viewBox="0 0 24 24" className="size-5 fill-current"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</button>
</div>
{result && (
<div className={`text-[11px] font-semibold p-2 rounded text-center mt-2 ${
result === "won" ? "bg-primary/20 text-primary" :
result === "lost" ? "bg-destructive/20 text-destructive" :
"bg-secondary text-muted-foreground"
}`}>
{result === "won" && "🎉 Bet Won!"}
{result === "lost" && "❌ Bet Lost"}
{result === "pending" && "⏳ Bet Pending"}
</div>
)}
</div>
</div>
)
}

View File

@ -1,80 +1,310 @@
import { Button } from "@/components/ui/button" "use client"
import { Card, CardContent } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area" import { useState, useEffect } from "react"
import { useSearchParams } from "next/navigation"
import { useBetslipStore } from "@/lib/store/betslip-store" import { useBetslipStore } from "@/lib/store/betslip-store"
import { mockEvents, popularLeagues, type Event } from "@/lib/mock-data"
import { cn } from "@/lib/utils"
import { ChevronDown, BarChart2, TrendingUp, Plus } from "lucide-react"
const mockEvents = [ function OddsButton({ odds, onClick, isSelected }: {
{ odds: number
id: "1", onClick: () => void
sport: "Soccer", isSelected: boolean
name: "Team A vs Team B", }) {
markets: [
{ id: "1x2_home", label: "1", odds: 1.85 },
{ id: "1x2_draw", label: "X", odds: 3.4 },
{ id: "1x2_away", label: "2", odds: 4.1 },
],
},
{
id: "2",
sport: "Basketball",
name: "City Wolves vs Lake Bears",
markets: [
{ id: "spread_home", label: "Home -4.5", odds: 1.9 },
{ id: "spread_away", label: "Away +4.5", odds: 1.9 },
],
},
]
export function EventsList() {
const { addBet } = useBetslipStore()
return ( return (
<Card className="flex-1"> <button
<CardContent className="px-0 pb-4 pt-3"> onClick={onClick}
<ScrollArea className="h-[360px]"> className={cn(
<div className="space-y-2 px-3 pb-2"> "flex items-center justify-center py-2 px-0.5 border-r border-border/10 text-[10px] transition-all min-w-0 bg-[#262626] hover:bg-[#333] h-full",
{mockEvents.map((event) => ( isSelected && "bg-[#ff9800] text-black font-bold border-none"
<div )}
key={event.id}
className="rounded-md border bg-card/50 p-2 text-xs"
> >
<div className="flex items-center justify-between"> <span className={cn("font-bold tracking-tighter", isSelected ? "text-black" : "text-[#ff9800]")}>{odds.toFixed(2)}</span>
<div> </button>
<div className="font-medium">{event.name}</div>
<div className="text-[11px] text-muted-foreground">
{event.sport}
</div>
</div>
</div>
<div className="mt-2 flex flex-wrap gap-1.5">
{event.markets.map((market) => (
<Button
key={market.id}
size="xs"
className="min-w-[60px] justify-between rounded-sm bg-secondary text-secondary-foreground text-[11px] hover:bg-primary hover:text-primary-foreground"
onClick={() =>
addBet({
id: `${event.id}-${market.id}`,
event: event.name,
market: event.sport,
selection: market.label,
odds: market.odds,
})
}
>
<span>{market.label}</span>
<span className="font-semibold">
{market.odds.toFixed(2)}
</span>
</Button>
))}
</div>
</div>
))}
</div>
</ScrollArea>
</CardContent>
</Card>
) )
} }
function EventRow({ event }: { event: Event }) {
const { bets, addBet } = useBetslipStore()
return (
<div className="bg-[#1a1a1a] border-b border-border/20 hover:bg-[#222] transition-colors h-[38px] flex items-center">
{/* Small Icons & ID Column */}
<div className="flex items-center gap-1.5 px-2 w-[80px] shrink-0 border-r border-border/10 h-full">
<BarChart2 className="size-3 text-muted-foreground hover:text-primary cursor-pointer shrink-0" />
<TrendingUp className="size-3 text-muted-foreground hover:text-primary cursor-pointer shrink-0" />
<span className="text-[9.5px] text-[#ff9800] font-bold tabular-nums italic ml-0.5">{event.id || "01682"}</span>
</div>
{/* Time & Team Column */}
<div className="flex items-center gap-3 px-3 w-[240px] shrink-0 border-r border-border/10 h-full">
<div className="flex flex-col text-[9.5px] font-bold text-white leading-tight italic shrink-0 w-[45px]">
<span>11:00</span>
<span className="text-white/40 uppercase font-medium">PM</span>
</div>
<span className="text-[10px] font-bold text-white truncate max-w-[180px]">{event.homeTeam} - {event.awayTeam}</span>
</div>
{/* Market Columns Grid (10 Markets) */}
<div className="flex-1 grid grid-cols-10 h-full shrink-0">
{event.markets.slice(0, 10).map((market) => {
const betId = `${event.id}-${market.id}`
const isSelected = bets.some((b) => b.id === betId)
return (
<OddsButton
key={market.id}
odds={market.odds}
isSelected={isSelected}
onClick={() => addBet({
id: betId,
event: `${event.homeTeam} vs ${event.awayTeam}`,
league: event.league,
market: "1X2",
selection: `${event.homeTeam} (${market.label})`,
odds: market.odds,
})}
/>
)
})}
</div>
{/* More Markets Button */}
<button className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-border/10 group">
<Plus className="size-3 text-white group-hover:scale-110 transition-transform" />
</button>
</div>
)
}
const MARKET_HEADERS = ["1", "x", "2", "Over (2.5)", "Under (2.5)", "1X", "12", "X2", "Yes", "No"];
export function EventsList({ filter = "All", sport = "all", search = "" }: {
filter?: string
sport?: string
search?: string
}) {
const searchParams = useSearchParams()
const leagueQuery = searchParams.get("league")
const [selectedLeague, setSelectedLeague] = useState<string | null>(leagueQuery)
const { bets, addBet } = useBetslipStore()
useEffect(() => {
setSelectedLeague(leagueQuery)
}, [leagueQuery])
const handleClose = () => {
const url = new URL(window.location.href)
url.searchParams.delete("league")
window.history.pushState({}, "", url)
setSelectedLeague(null)
}
const events = selectedLeague
? mockEvents.filter(e => e.league.toLowerCase() === selectedLeague.toLowerCase())
: mockEvents.filter((e) => {
if (filter === "Live" && !e.isLive) return false
if (sport !== "all" && e.sport.toLowerCase() !== sport.toLowerCase()) return false
return true
})
// Common Header Rendering
const renderTableHeaders = () => (
<>
{/* Table Header Categories */}
<div className="bg-[#1a1a1a] border-b border-border/40 grid grid-cols-5 text-[11px] font-bold text-white uppercase text-center items-center h-9">
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Main</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Goals</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Handicap</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20 text-[9px] leading-tight">Half Time / Full Time</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer">Correct Score</div>
</div>
{/* Sub Headers */}
<div className="bg-[#1a1a1a] border-b border-border/40 grid grid-cols-5 text-[10px] font-bold text-white uppercase text-center items-center h-8">
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">1st Half</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">2nd Half</div>
<div className="h-full flex items-center justify-center bg-[#ff9800] text-black border-r border-border/10">Combo</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer border-r border-border/20">Chance Mix</div>
<div className="h-full flex items-center justify-center hover:bg-[#222] transition-colors cursor-pointer">Home</div>
</div>
</>
)
const renderColumnHeaders = () => (
<div className="bg-[#222] border-b border-white/5 h-8 flex items-center text-[9px] font-black text-white/40 uppercase">
<div className="w-[180px] px-3 flex items-center gap-1.5 border-r border-border/10 h-full">Main</div>
<div className="w-[180px] flex items-center justify-center border-r border-border/10 h-full">Over/Under</div>
<div className="flex-1 grid grid-cols-10 text-center h-full items-center tracking-tighter">
{MARKET_HEADERS.map(h => <span key={h}>{h}</span>)}
</div>
<div className="w-10 border-l border-border/10 h-full" />
</div>
)
const renderEventItem = (event: Event) => (
<div key={event.id} className="h-[34px] group flex items-center border-b border-white/5 bg-[#121212] hover:bg-white/5 transition-colors">
{/* Stats & Icons */}
<div className="w-[35px] flex items-center justify-center gap-1 px-2 border-r border-white/5 h-full opacity-40 group-hover:opacity-100">
<BarChart2 className="size-3 cursor-pointer hover:text-primary" />
</div>
{/* ID */}
<div className="w-[45px] text-[10px] font-black text-[#ff9800] italic tabular-nums text-center border-r border-white/5 h-full flex items-center justify-center">
{event.id}
</div>
{/* Time */}
<div className="w-[50px] flex flex-col items-center justify-center border-r border-white/5 h-full leading-none italic font-black text-[9px]">
<span>{event.time}</span>
<span className="text-[7px] text-white/30 uppercase mt-0.5">PM</span>
</div>
{/* Event Name */}
<div className="flex-1 px-4 text-[10.5px] font-black text-white truncate max-w-[200px]">
{event.homeTeam} - {event.awayTeam}
</div>
{/* Odds Grid */}
<div className="flex-1 grid grid-cols-10 h-full">
{event.markets.slice(0, 10).map((market) => {
const betId = `${event.id}-${market.id}`
const isSelected = bets.some((b) => b.id === betId)
return (
<button
key={market.id}
onClick={() => addBet({
id: betId,
event: `${event.homeTeam} vs ${event.awayTeam}`,
league: event.league,
market: "1X2",
selection: `${event.homeTeam} (${market.label})`,
odds: market.odds,
})}
className={cn(
"flex items-center justify-center text-[10.5px] font-black tabular-nums transition-all border-r border-white/5",
isSelected ? "bg-[#ff9800] text-black" : "text-[#ff9800] hover:bg-white/5"
)}
>
{market.odds.toFixed(2)}
</button>
)
})}
</div>
{/* More Button */}
<button className="w-10 flex items-center justify-center h-full hover:bg-white/5 transition-colors border-l border-white/5 text-white/40">
<Plus className="size-3" />
</button>
</div>
)
if (selectedLeague) {
// Group by date for league view
const groupedEvents = events.reduce((acc, event) => {
if (!acc[event.date]) acc[event.date] = []
acc[event.date].push(event)
return acc
}, {} as Record<string, Event[]>)
return (
<div className="flex flex-col bg-[#121212] rounded overflow-hidden shadow-2xl">
{/* League Header / Breadcrumbs */}
<div className="bg-[#1a1a1a] px-3 py-2 flex items-center justify-between border-b border-border/20">
<div className="flex items-center gap-2 text-[11px] font-bold text-white/60">
<Plus className="size-3 cursor-pointer hover:text-white" />
<div className="flex items-center gap-1.5">
<span className="text-white font-black"></span>
<span className="uppercase">Football</span>
<span className="mx-1 opacity-20">|</span>
<span className="text-white uppercase">{selectedLeague === "LaLiga" ? "Spain - LaLiga" : selectedLeague}</span>
</div>
</div>
<button onClick={handleClose} className="text-white hover:text-primary transition-colors">
<Plus className="size-5 rotate-45" />
</button>
</div>
{/* Large Market Tab Grid */}
<div className="grid grid-cols-5 bg-[#121212] border-b border-border/10">
{[
{ label: "Main", active: true }, { label: "Goals" }, { label: "Handicap" }, { label: "Half Time / Full Time" }, { label: "Correct Score" },
{ label: "1st Half" }, { label: "2nd Half" }, { label: "Asian Markets" }, { label: "Corners" }, { label: "Home" }
].map((m, i) => (
<button
key={i}
className={cn(
"h-8 border-r border-b border-border/10 flex items-center justify-center text-[10px] font-black uppercase transition-all",
m.active ? "bg-[#ff9800] text-black" : "text-white/60 hover:bg-[#222]"
)}
>
{m.label}
</button>
))}
</div>
{/* Column Headers */}
{renderColumnHeaders()}
{/* Grouped Events */}
<div className="overflow-y-auto max-h-[700px]">
{Object.entries(groupedEvents).map(([date, dateEvents]) => (
<div key={date} className="flex flex-col">
<div className="bg-[#1a1a1a] px-2 py-1 text-[10px] font-black text-white border-b border-white/5">
{date}
</div>
{dateEvents.map(event => renderEventItem(event))}
</div>
))}
</div>
</div>
)
}
// Home View (No League Selected)
const homeEventsByLeague = events.reduce((acc, event) => {
if (!acc[event.league]) acc[event.league] = []
acc[event.league].push(event)
return acc
}, {} as Record<string, Event[]>)
return (
<div className="flex flex-col bg-[#121212] rounded overflow-hidden">
<div className="flex flex-col">
{Object.entries(homeEventsByLeague).map(([leagueName, leagueEvents]) => (
<div key={leagueName} className="flex flex-col border-b border-white/5 last:border-none">
{/* League Box Header */}
<div className="bg-[#2a2a2a] px-3 py-1.5 text-[10px] font-bold text-[#ff9800] uppercase border-y border-border/20 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-[14px]">
{popularLeagues.find(l => l.name === leagueName)?.icon ||
popularLeagues.find(l => l.id.toLowerCase() === leagueName.toLowerCase())?.icon ||
"⚽"}
</span>
<span>{leagueName === "LaLiga" ? "Spain - LaLiga" :
leagueName === "Premier League" ? "England - Premier League" :
leagueName === "Bundesliga" ? "Germany - Bundesliga" :
leagueName === "Ligue 1" ? "France - Ligue 1" :
leagueName}</span>
</div>
<ChevronDown className="size-4 text-white" />
</div>
{/* Column Headers for each league box */}
<div className="bg-[#222] px-3 py-1 flex items-center text-[8px] font-black text-white/40 uppercase border-b border-border/20">
<div className="w-[180px] flex gap-4">
<span className="w-5 text-center">Stats</span>
<span className="w-6">ID</span>
<span className="w-10">Time</span>
<span>Event</span>
</div>
<div className="flex-1 grid grid-cols-10 text-center tracking-tighter">
{MARKET_HEADERS.map(h => <span key={h}>{h}</span>)}
</div>
<div className="w-10" />
</div>
{/* Matches in this league */}
<div className="flex flex-col">
{leagueEvents.map(event => renderEventItem(event))}
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,62 @@
"use client"
import { useState, useEffect } from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
const images = [
"https://api-new.harifsport.com/home/img/editable/casj-1822.jpg",
"https://api-new.harifsport.com/home/img/editable/casj-18s.jpg"
]
export function HeroBanner() {
const [currentIndex, setCurrentIndex] = useState(0)
useEffect(() => {
const timer = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % images.length)
}, 5000)
return () => clearInterval(timer)
}, [])
return (
<div className="w-full h-[240px] md:h-[300px] relative overflow-hidden rounded-none group shadow-lg bg-[#111]">
{images.map((src, index) => (
<img
key={src}
src={src}
alt={`Banner ${index + 1}`}
className={`absolute inset-0 object-contain w-full h-full transition-opacity duration-1000 ${
index === currentIndex ? "opacity-100" : "opacity-0"
}`}
/>
))}
{/* Navigation Arrows */}
<button
onClick={() => setCurrentIndex((prev) => (prev - 1 + images.length) % images.length)}
className="absolute left-4 top-1/2 -translate-y-1/2 bg-black/60 p-2 text-white/80 hover:text-white transition-colors opacity-0 group-hover:opacity-100"
>
<ChevronLeft className="size-6" />
</button>
<button
onClick={() => setCurrentIndex((prev) => (prev + 1) % images.length)}
className="absolute right-4 top-1/2 -translate-y-1/2 bg-black/60 p-2 text-white/80 hover:text-white transition-colors opacity-0 group-hover:opacity-100"
>
<ChevronRight className="size-6" />
</button>
{/* Indicators */}
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-1.5 px-3 py-1.5 rounded-full bg-black/30 backdrop-blur-sm">
{images.map((_, index) => (
<button
key={index}
onClick={() => setCurrentIndex(index)}
className={`w-1.5 h-1.5 rounded-full transition-all ${
index === currentIndex ? "bg-[#ff9800] scale-125" : "bg-white/40"
}`}
/>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,24 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
const tabs = [
{ id: "today", label: "Today Events" },
{ id: "upcoming", label: "Upcoming Events" },
{ id: "most-played", label: "Most Played" },
{ id: "top-occurrences", label: "Top Occurrences" },
]
export function HomeTabs() {
return (
<div className="flex flex-col">
<Tabs defaultValue="today" className="w-full">
<TabsList variant="hs-home">
{tabs.map((tab) => (
<TabsTrigger key={tab.id} value={tab.id}>
{tab.label}
</TabsTrigger>
))}
</TabsList>
</Tabs>
</div>
)
}

View File

@ -1,11 +1,12 @@
export function InPlayHeader() { export function InPlayHeader() {
return ( return (
<div className="flex items-center justify-between border-b pb-2"> <div className="flex items-center justify-between border-b border-border pb-2">
<h1 className="text-sm font-semibold uppercase tracking-wide text-foreground"> <div className="flex items-center gap-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">
IN-PLAY IN-PLAY
</h1> </h1>
<span className="text-[11px] text-muted-foreground">Quick Filter</span> <span className="text-[10px] text-muted-foreground">/ Today&apos;s Events</span>
</div>
</div> </div>
) )
} }

View File

@ -0,0 +1,195 @@
"use client"
import { useBetslipStore } from "@/lib/store/betslip-store"
import { mockEvents, type Event } from "@/lib/mock-data"
import { cn } from "@/lib/utils"
import { BarChart2, TrendingUp, Monitor, Tv } from "lucide-react"
function LiveEventRow({ event, isNoOdds }: { event: Event, isNoOdds?: boolean }) {
const { bets, addBet } = useBetslipStore()
// Dummy data for demonstration
const score = event.homeScore !== undefined ? `${event.homeScore} - ${event.awayScore}` : "0 - 0"
const time = event.liveMinute ? `${event.liveMinute}:00` : "83:10"
const period = "H2"
return (
<div className="bg-[#121212] border-b border-white/5 hover:bg-white/5 transition-colors h-[50px] flex items-center group">
{/* Match Info Column (Time & Score) */}
<div className="flex items-center gap-3 px-3 w-[360px] shrink-0 h-full border-r border-white/5">
<div className="flex flex-col text-[10px] font-black leading-tight italic w-[55px] shrink-0 tabular-nums">
<span className="text-[#ff9800]">{time}</span>
<span className="text-white/40">{period}</span>
</div>
<div className="flex items-center min-w-0 flex-1 gap-2">
<span className="text-[11.5px] font-black text-white truncate italic uppercase">
{event.homeTeam} <span className="text-[#ff9800] mx-1 tabular-nums">{score}</span> {event.awayTeam}
</span>
</div>
<div className="flex items-center gap-2 shrink-0 px-1 opacity-20 group-hover:opacity-100 transition-opacity">
<BarChart2 className="size-3.5 text-white" />
<Monitor className="size-3.5 text-white" />
</div>
</div>
{/* Odds Grid or Placeholder */}
<div className="flex-1 h-full flex items-center">
{isNoOdds ? (
<div className="flex-1 h-full bg-[#161616] flex items-center justify-center text-[10.5px] font-black text-white/20 uppercase italic tracking-tight">
Sorry, no odds for this match
</div>
) : (
<div className="flex-1 grid grid-cols-3 h-full">
{event.markets.slice(0, 3).map((m, idx) => {
const labels = ["Home", "Draw", "Away"]
return (
<button
key={m.id}
onClick={() => addBet({
id: `${event.id}-${m.id}`,
event: `${event.homeTeam} vs ${event.awayTeam}`,
league: event.league,
market: "1X2",
selection: `${m.label}`,
odds: m.odds,
})}
className="bg-[#1a1a1a] hover:bg-[#2a2a2a] flex items-center justify-between px-4 h-full border-r border-white/5 transition-colors group/btn"
>
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white uppercase">{labels[idx]}</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{m.odds.toFixed(2)}</span>
</button>
)
})}
</div>
)}
</div>
{/* Right Indicator */}
<div className="w-[4px] bg-[#ff9800] h-full" />
</div>
)
}
const liveSports = [
{ id: "soccer", label: "Soccer", icon: "⚽", count: 25, active: true },
{ id: "basketball", label: "Basketball", icon: "🏀", count: 39 },
{ id: "ice-hockey", label: "Ice Hockey", icon: "🏒", count: 3 },
{ id: "tennis", label: "Tennis", icon: "🎾", count: 4 },
{ id: "handball", label: "Handball", icon: "🤾", count: 10 },
{ id: "rugby", label: "Rugby", icon: "🏉", count: 2 },
{ id: "table-tennis", label: "Table Tennis", icon: "🏓", count: 8 },
{ id: "volleyball", label: "Volleyball", icon: "🏐", count: 7 },
{ id: "futsal", label: "Futsal", icon: "⚽", count: 2 },
{ id: "esport-counter-strike", label: "ESport Cou...", icon: "🎮", count: 2 },
{ id: "esport-league-of-legends", label: "ESport Lea...", icon: "🎮", count: 1 },
{ id: "esport-dota-2", label: "ESport Dota", icon: "🎮", count: 1 },
{ id: "efootball", label: "eFootball", icon: "⚽", count: 4 },
{ id: "ebasketball", label: "eBasketball", icon: "🏀", count: 1 },
]
export function LiveEventsList() {
// Enhanced mock data local to live view to match screenshot exactly
const liveMatches = [
{
league: "Algeria - Ligue 1",
flag: "https://flagcdn.com/w20/dz.png",
matches: [
{ ...mockEvents[0], id: "l1", homeTeam: "Paradou AC", awayTeam: "Ben Aknoun", homeScore: 3, awayScore: 5, liveMinute: 91, noOdds: true }
]
},
{
league: "Australia - U23 Victoria NPL",
flag: "https://flagcdn.com/w20/au.png",
matches: [
{ ...mockEvents[1], id: "l2", homeTeam: "Oakleigh Cannons FC", awayTeam: "Altona Magic SC", homeScore: 5, awayScore: 1, liveMinute: 87, noOdds: true }
]
},
{
league: "Australia - U23 Victoria Premier League 1",
flag: "https://flagcdn.com/w20/au.png",
matches: [
{ ...mockEvents[2], id: "l3", homeTeam: "Northcote City FC", awayTeam: "Western United FC", homeScore: 4, awayScore: 0, liveMinute: 83, noOdds: false },
{ ...mockEvents[3], id: "l4", homeTeam: "Melbourne Knights FC", awayTeam: "Melbourne Victory FC", homeScore: 0, awayScore: 3, liveMinute: 81, noOdds: true }
]
},
{
league: "Australia - Victoria NPL, Women",
flag: "https://flagcdn.com/w20/au.png",
matches: [
{ ...mockEvents[4], id: "l5", homeTeam: "Preston Lions FC", awayTeam: "South Melbourne FC", homeScore: 1, awayScore: 1, liveMinute: 52, noOdds: true },
{ ...mockEvents[0], id: "l6", homeTeam: "Bentleigh Greens SC", awayTeam: "Box Hill United", homeScore: 0, awayScore: 6, liveMinute: 83, noOdds: true }
]
}
]
return (
<div className="flex flex-col min-h-screen bg-[#111]">
{/* Sport Navigation Carousel */}
<div className="bg-[#1a1a1a] border-b border-border/20 px-2 flex items-center h-[54px] overflow-x-auto scrollbar-hide">
<div className="flex items-center gap-0 h-full">
{/* Favourites & Prematch */}
<button className="flex flex-col items-center justify-center px-4 h-full border-r border-white/5 min-w-[70px]">
<span className="text-[14px]"></span>
<span className="text-[9px] font-bold text-white/40 uppercase mt-0.5">Favourites</span>
</button>
<button className="flex flex-col items-center justify-center px-4 h-full border-r border-white/5 min-w-[70px]">
<span className="text-[14px]"></span>
<span className="text-[9px] font-bold text-white/40 uppercase mt-0.5">Prematch</span>
</button>
{/* Live Sports */}
{liveSports.map((sport) => (
<button
key={sport.id}
className={cn(
"flex flex-col items-center justify-center px-3 h-full border-r border-white/5 min-w-[75px] relative transition-colors",
sport.active ? "bg-black/20" : "hover:bg-white/5"
)}
>
<span className="absolute top-1 right-2 text-[8.5px] font-black text-white/40">{sport.count}</span>
<span className="text-[16px]">{sport.icon}</span>
<span className={cn(
"text-[9px] font-bold uppercase mt-1 tracking-tighter whitespace-nowrap",
sport.active ? "text-[#ff9800]" : "text-white/40"
)}>
{sport.label}
</span>
{sport.active && (
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-[#ff9800]" />
)}
</button>
))}
</div>
</div>
{/* Category Header (Soccer) */}
<div className="bg-[#009688] px-3 py-1.5 flex items-center gap-2 border-l-[4px] border-[#ff9800]">
<span className="text-[16px]"></span>
<h2 className="text-[14px] font-black text-white uppercase tracking-tight">Soccer</h2>
</div>
{/* Grouped Live Matches */}
<div className="flex flex-col mb-10">
{liveMatches.map((group, gIdx) => (
<div key={gIdx} className="flex flex-col">
{/* League Header */}
<div className="bg-[#1a1a1a] px-3 py-1 border-b border-border/10 flex items-center gap-2">
<img src={group.flag} width="14" alt={group.league} className="rounded-sm opacity-60" />
<span className="text-[9.5px] font-black text-white/60 uppercase tracking-widest leading-none">
{group.league}
</span>
</div>
{/* Matches in this league */}
<div className="flex flex-col">
{group.matches.map((match, mIdx) => (
<LiveEventRow key={match.id} event={match as any} isNoOdds={match.noOdds} />
))}
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -1,18 +1,32 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" "use client"
const filters = ["All", "Today", "3h", "6h", "9h", "12h"] import { cn } from "@/lib/utils"
export function QuickFilterBar() { const filters = ["All", "Live", "Today", "3h", "6h", "12h"]
export function QuickFilterBar({ active, onChange }: {
active: string
onChange: (f: string) => void
}) {
return ( return (
<Tabs defaultValue="All" className="w-full"> <div className="flex items-center gap-1 overflow-x-auto pb-0.5">
<TabsList className="h-8">
{filters.map((filter) => ( {filters.map((filter) => (
<TabsTrigger key={filter} value={filter} className="px-3 text-xs"> <button
key={filter}
onClick={() => onChange(filter)}
className={cn(
"shrink-0 px-3 py-1 rounded text-[11px] font-semibold transition-colors",
active === filter
? "bg-primary text-primary-foreground"
: "bg-secondary text-muted-foreground hover:bg-muted hover:text-foreground"
)}
>
{filter} {filter}
</TabsTrigger> {filter === "Live" && (
<span className="ml-1 live-dot inline-block size-1.5 rounded-full bg-[var(--hs-live-red)] align-middle" />
)}
</button>
))} ))}
</TabsList> </div>
</Tabs>
) )
} }

View File

@ -1,28 +1,27 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" "use client"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button" import { useState } from "react"
export function ReloadTicket() { export function ReloadTicket() {
const [code, setCode] = useState("")
return ( return (
<Card> <div className="bg-transparent border-t border-border/20 pt-4 pb-2">
<CardHeader> <div className="px-1 text-center space-y-3">
<CardTitle className="text-sm">Reload Ticket</CardTitle> <h3 className="text-[12px] font-bold uppercase text-[#ff9800]">Reload Ticket</h3>
</CardHeader> <p className="text-[11px] text-white">Insert the code to load</p>
<CardContent className="space-y-2 text-xs"> <div className="flex gap-1">
<div className="text-[11px] text-muted-foreground"> <input
Insert the code to load type="text"
</div> value={code}
<div className="flex gap-2"> onChange={(e) => setCode(e.target.value)}
<Input className="flex-1 bg-[#121212] border border-border/40 px-2 py-2 text-[11px] text-white outline-none focus:border-primary"
placeholder="Ticket code"
className="h-8 text-xs placeholder:text-[11px]"
/> />
<Button size="sm" className="text-xs"> <button className="bg-[#ff9800] text-black px-2 py-1.5 flex items-center justify-center min-w-[32px] hover:bg-[#ffa726] transition-colors">
Reload <svg viewBox="0 0 24 24" className="size-5 fill-current"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
</Button> </button>
</div>
</div>
</div> </div>
</CardContent>
</Card>
) )
} }

View File

@ -1,19 +1,21 @@
import { Input } from "@/components/ui/input" "use client"
export function SearchEvent() { import { Search } from "lucide-react"
export function SearchEvent({ value, onChange }: {
value: string
onChange: (v: string) => void
}) {
return ( return (
<div className="space-y-1 rounded-md border bg-card/60 p-3"> <div className="relative">
<div className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground"> <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
Search Event <input
</div> type="text"
<p className="text-[11px] text-muted-foreground"> value={value}
Insert the events name or at least one team in the form below onChange={(e) => onChange(e.target.value)}
</p> placeholder="Search by event, team or league..."
<Input className="w-full bg-secondary border border-border rounded pl-8 pr-3 py-2 text-[12px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors"
placeholder="Search by event or team"
className="h-8 text-xs placeholder:text-[11px]"
/> />
</div> </div>
) )
} }

View File

@ -0,0 +1,27 @@
"use client"
import { useSearchParams } from "next/navigation"
import { HeroBanner } from "./hero-banner"
import { TopMatches } from "./top-matches"
import { SportsNav } from "./sports-nav"
import { HomeTabs } from "./home-tabs"
import { EventsList } from "./events-list"
export function SportHome() {
const searchParams = useSearchParams()
const isLeagueView = !!searchParams.get("league")
return (
<div className="flex flex-col gap-3">
{!isLeagueView && (
<>
<HeroBanner />
<TopMatches />
<SportsNav />
<HomeTabs />
</>
)}
<EventsList />
</div>
)
}

View File

@ -0,0 +1,33 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
const sports = [
{ id: "football", name: "Football", icon: "⚽" },
{ id: "tennis", name: "Tennis", icon: "🎾" },
{ id: "basketball", name: "Basketball", icon: "🏀" },
{ id: "ice-hockey", name: "Ice Hockey", icon: "🏒" },
{ id: "mma", name: "MMA", icon: "🥋" },
{ id: "handball", name: "Handball", icon: "🤾" },
{ id: "darts", name: "Darts", icon: "🎯" },
{ id: "snooker", name: "Snooker", icon: "🎱" },
{ id: "cricket", name: "Cricket", icon: "🏏" },
{ id: "dota2", name: "Dota 2", icon: "🎮" },
]
export function SportsNav() {
return (
<Tabs defaultValue="football" className="w-full">
<TabsList variant="hs-nav" className="h-auto">
{sports.map((sport) => (
<TabsTrigger
key={sport.id}
value={sport.id}
className="flex-col min-w-[70px] py-2 gap-1"
>
<span className="text-xl">{sport.icon}</span>
<span className="text-[10px] font-bold uppercase">{sport.name}</span>
</TabsTrigger>
))}
</TabsList>
</Tabs>
)
}

View File

@ -0,0 +1,102 @@
import { ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/button"
const topMatches = [
{
id: "tm1",
league: "England - Premier League",
time: "05:00 PM",
homeTeam: "Nottingham Forest",
awayTeam: "Liverpool",
odds: { home: 4.09, draw: 3.93, away: 1.82 }
},
{
id: "tm2",
league: "England - Premier League",
time: "11:00 PM",
homeTeam: "Man City",
awayTeam: "Newcastle",
odds: { home: 1.50, draw: 5.17, away: 5.93 }
},
{
id: "tm3",
league: "England - Premier League",
time: "06:00 PM",
homeTeam: "Chelsea",
awayTeam: "Burnley",
odds: { home: 1.21, draw: 6.91, away: 11.50 }
},
{
id: "tm4",
league: "Spain - LaLiga",
time: "07:30 PM",
homeTeam: "Arsenal",
awayTeam: "Wolves",
odds: { home: 1.56, draw: 4.16, away: 5.80 }
},
{
id: "tm5",
league: "Italy - Serie A",
time: "09:45 PM",
homeTeam: "Inter Milan",
awayTeam: "Napoli",
odds: { home: 1.85, draw: 3.60, away: 4.20 }
}
]
export function TopMatches() {
return (
<div className="flex gap-3 overflow-x-auto pb-2 scrollbar-hide -mx-1 px-1">
{topMatches.map((match) => (
<div
key={match.id}
className="min-w-[280px] bg-[#222] border border-border/20 rounded-sm overflow-hidden flex flex-col relative group"
>
{/* Top Label Ribbon */}
<div className="absolute top-0 right-0 w-12 h-12 overflow-hidden z-10 pointer-events-none">
<div className="absolute top-[6px] right-[-14px] bg-[#ff9800] text-black text-[8px] font-black py-0.5 px-6 rotate-45 shadow-sm uppercase tracking-tighter">
TOP
</div>
</div>
<div className="bg-[#1a1a1a] px-3 py-1.5 flex items-center justify-between text-[10px] text-muted-foreground border-b border-border/10">
<div className="flex items-center gap-1">
<span className="font-bold text-[#ff9800]">{match.league}</span>
<span className="font-black text-white ml-1 italic">{match.time}</span>
</div>
<ChevronRight className="size-3 text-white/20 mr-4" />
</div>
<div className="p-3 flex flex-col gap-3">
<div className="flex items-center justify-between gap-1">
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10"></div>
<span className="text-[11px] font-black text-white truncate uppercase italic">{match.homeTeam}</span>
</div>
<span className="text-[#ff9800] text-[9.5px] font-black italic shrink-0 px-2 opacity-60">VS</span>
<div className="flex items-center gap-2 flex-1 min-w-0 justify-end">
<span className="text-[11px] font-black text-white truncate uppercase italic text-right">{match.awayTeam}</span>
<div className="size-4 shrink-0 bg-white/5 rounded-sm flex items-center justify-center text-[9px] border border-white/10"></div>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-[1px] bg-white/5 mx-2 mb-2 rounded-sm overflow-hidden border border-white/5">
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors group/btn">
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">1</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.home.toFixed(2)}</span>
</button>
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors border-x border-white/5 group/btn">
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">X</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.draw.toFixed(2)}</span>
</button>
<button className="bg-[#1a1a1a] hover:bg-[#333] flex items-center justify-between px-2.5 py-1.5 transition-colors group/btn">
<span className="text-[10px] font-black text-white/40 group-hover/btn:text-white">2</span>
<span className="text-[11px] font-black text-[#ff9800] tabular-nums">{match.odds.away.toFixed(2)}</span>
</button>
</div>
</div>
))}
</div>
)
}

View File

@ -0,0 +1,215 @@
"use client"
import { useState, useEffect, useCallback } from "react"
import Link from "next/link"
type Mode = "login" | "register"
function Logo() {
return (
<div className="flex items-center">
{/* Red slashes */}
<div className="flex gap-[3px] mr-2">
{[0, 1, 2].map((i) => (
<div key={i} className="w-[5px] h-[38px] bg-[#cc2222] -skew-x-12" />
))}
</div>
{/* HARIF box */}
<div className="bg-brand-accent px-3 py-1 -skew-x-12 flex items-center h-[38px]">
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">
HARIF
</span>
</div>
{/* SPORT text */}
<span className="text-2xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">
SPORT
</span>
</div>
)
}
interface AuthModalProps {
open: boolean
defaultMode: Mode
onClose: () => void
}
export function AuthModal({ open, defaultMode, onClose }: AuthModalProps) {
const [mode, setMode] = useState<Mode>(defaultMode)
const [phone, setPhone] = useState("")
const [password, setPassword] = useState("")
const [repeatPassword, setRepeatPassword] = useState("")
const [ageConfirmed, setAgeConfirmed] = useState(false)
// Sync mode if parent changes defaultMode while open
useEffect(() => {
if (open) setMode(defaultMode)
}, [defaultMode, open])
// Close on Escape
const handleKey = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") onClose()
},
[onClose]
)
useEffect(() => {
if (open) window.addEventListener("keydown", handleKey)
return () => window.removeEventListener("keydown", handleKey)
}, [open, handleKey])
if (!open) return null
return (
<div
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70"
onClick={onClose}
>
<div
className="relative w-[420px] bg-brand-surface-light shadow-2xl animate-fade-in"
onClick={(e) => e.stopPropagation()}
>
{/* Close button */}
<button
onClick={onClose}
className="absolute top-2 right-3 text-white/60 hover:text-white text-xl leading-none transition-colors z-10"
aria-label="Close"
>
×
</button>
{/* Logo */}
<div className="flex items-center justify-center py-5 border-b border-white/10 bg-brand-surface">
<Logo />
</div>
{/* Title */}
<div className="text-center py-4">
<h2 className="text-sm font-black text-white uppercase tracking-widest">
{mode === "login" ? "LOGIN" : "REGISTER"}
</h2>
</div>
{/* Form */}
<div className="px-8 pb-6 space-y-4">
{/* Phone Number */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Phone Number
</label>
<div className="flex">
<span className="flex items-center justify-center bg-white text-[#333] text-[12px] font-bold px-3 border border-gray-300 whitespace-nowrap select-none">
ET +251
</span>
<input
type="tel"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="flex-1 bg-white border border-l-0 border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
/>
</div>
</div>
{/* Password */}
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
/>
</div>
{/* Repeat Password (register only) */}
{mode === "register" && (
<div>
<label className="block text-[11px] font-semibold text-white/80 mb-1">
Repeat Password
</label>
<input
type="password"
value={repeatPassword}
onChange={(e) => setRepeatPassword(e.target.value)}
placeholder="Repeat Password"
className="w-full bg-white border border-gray-300 px-3 py-2 text-sm text-[#333] placeholder:text-gray-400 focus:outline-none focus:border-brand-primary"
/>
</div>
)}
{/* Age confirmation (register only) */}
{mode === "register" && (
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={ageConfirmed}
onChange={(e) => setAgeConfirmed(e.target.checked)}
className="w-4 h-4 accent-brand-primary"
/>
<span className="text-[12px] text-white/80">
I confirm I&apos;m over 21 years old
</span>
</label>
)}
{/* Forgot password (login only) */}
{mode === "login" && (
<div className="text-right -mt-2">
<Link
href="/reset-password"
className="text-[11px] text-white/50 hover:text-brand-primary transition-colors"
onClick={onClose}
>
Forgot password?
</Link>
</div>
)}
{/* Submit button */}
<button className="w-full bg-brand-primary hover:bg-brand-primary-hover text-black font-black py-3 uppercase text-sm tracking-widest transition-colors">
{mode === "login" ? "LOGIN" : "REGISTER"}
</button>
{/* Support link */}
<p className="text-center text-[12px] text-white/60">
<Link
href="/support"
className="hover:text-white transition-colors"
onClick={onClose}
>
Support
</Link>
</p>
{/* Toggle mode */}
<p className="text-center text-[11px] text-white/50">
{mode === "login" ? (
<>
Don&apos;t have an account?{" "}
<button
className="text-brand-primary hover:underline font-semibold"
onClick={() => setMode("register")}
>
Register
</button>
</>
) : (
<>
Already have an account?{" "}
<button
className="text-brand-primary hover:underline font-semibold"
onClick={() => setMode("login")}
>
Login
</button>
</>
)}
</p>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,58 @@
"use client"
import { usePathname } from "next/navigation"
import { useState, useCallback } from "react"
import { SiteHeader } from "@/components/layout/site-header"
import { SportsSidebar } from "@/components/layout/sports-sidebar"
import { RightPanel } from "@/components/layout/right-panel"
import { SiteFooter } from "@/components/layout/site-footer"
import { AuthModal } from "@/components/layout/auth-modal"
import { MobileBottomNav } from "@/components/layout/mobile-bottom-nav"
type AuthMode = "login" | "register"
export default function LayoutClientWrapper({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const isLivePage = pathname === "/live"
const [authOpen, setAuthOpen] = useState(false)
const [authMode, setAuthMode] = useState<AuthMode>("login")
const openAuth = useCallback((mode: AuthMode) => {
setAuthMode(mode)
setAuthOpen(true)
}, [])
return (
<div className="flex min-h-screen flex-col pb-14 md:pb-0">
<SiteHeader
onLoginClick={() => openAuth("login")}
onRegisterClick={() => openAuth("register")}
/>
<div className="flex w-full flex-1 gap-0">
{/* Sidebar: hidden on mobile */}
{!isLivePage && (
<div className="hidden md:block">
<SportsSidebar />
</div>
)}
<main className="flex-1 min-w-0 px-2 py-3">{children}</main>
{/* Right panel: hidden on mobile */}
<div className="hidden md:block">
<RightPanel />
</div>
</div>
<SiteFooter />
{/* Mobile fixed bottom nav */}
<MobileBottomNav />
{/* Auth modal */}
<AuthModal
open={authOpen}
defaultMode={authMode}
onClose={() => setAuthOpen(false)}
/>
</div>
)
}

View File

@ -0,0 +1,80 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
const bottomNavItems = [
{
href: "/",
label: "Home",
icon: (active: boolean) => (
<svg viewBox="0 0 24 24" className={cn("size-6", active ? "fill-brand-primary" : "fill-white/60")}>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</svg>
),
},
{
href: "/sports",
label: "Sports",
icon: (active: boolean) => (
<svg viewBox="0 0 24 24" className={cn("size-6", active ? "fill-brand-primary" : "fill-white/60")}>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
</svg>
),
},
{
href: "/betslip",
label: "Betslip",
icon: (active: boolean) => (
<svg viewBox="0 0 24 24" className={cn("size-6", active ? "fill-brand-primary" : "fill-white/60")}>
<path d="M20 4H4c-1.11 0-2 .89-2 2v12c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"/>
</svg>
),
},
{
href: "/games",
label: "Casino",
icon: (active: boolean) => (
<svg viewBox="0 0 24 24" className={cn("size-6", active ? "fill-brand-primary" : "fill-white/60")}>
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14l-5-5 1.41-1.41L12 14.17l7.59-7.59L21 8l-9 9z"/>
</svg>
),
},
{
href: "/support",
label: "Help",
icon: (active: boolean) => (
<svg viewBox="0 0 24 24" className={cn("size-6", active ? "fill-brand-primary" : "fill-white/60")}>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/>
</svg>
),
},
]
export function MobileBottomNav() {
const pathname = usePathname()
return (
<nav className="fixed bottom-0 left-0 right-0 z-50 flex md:hidden h-14 bg-brand-bg border-t border-white/10">
{bottomNavItems.map((item) => {
const isActive = pathname === item.href
return (
<Link
key={item.href}
href={item.href}
className="flex-1 flex flex-col items-center justify-center gap-0.5 transition-colors"
>
{item.icon(isActive)}
<span className={cn(
"text-[10px] font-semibold",
isActive ? "text-brand-primary" : "text-white/60"
)}>
{item.label}
</span>
</Link>
)
})}
</nav>
)
}

View File

@ -1,24 +1,61 @@
"use client"
import { Betslip } from "@/components/betting/betslip" import { Betslip } from "@/components/betting/betslip"
import { ReloadTicket } from "@/components/betting/reload-ticket" import { ReloadTicket } from "@/components/betting/reload-ticket"
import { CheckYourBet } from "@/components/betting/check-your-bet" import { CheckYourBet } from "@/components/betting/check-your-bet"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
export function RightPanel() { export function RightPanel() {
const [activeTab, setActiveTab] = useState<"betslip" | "myBets">("betslip")
return ( return (
<aside className="flex w-full flex-col gap-3 border-t bg-sidebar px-3 py-3 lg:h-full lg:w-72 lg:shrink-0 lg:border-l lg:border-t-0 lg:pl-4"> <aside className="hidden lg:flex w-[280px] shrink-0 flex-col gap-0 border-l border-border/30 bg-[#222]">
<Betslip /> {/* Search Header */}
<Card> <div className="p-3 bg-[#1a1a1a] flex items-center justify-between border-b border-border/20">
<CardHeader> <div className="flex items-center gap-2 text-[12px] font-bold text-white uppercase">
<CardTitle className="text-sm">Settings</CardTitle> Fast Bet <span className="text-[#ff9800]">QBET</span>
</CardHeader> <svg viewBox="0 0 24 24" className="size-4 fill-[#ff9800] ml-1"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
<CardContent className="text-xs text-muted-foreground"> </div>
Decimal odds (more settings coming soon) </div>
</CardContent>
</Card> <div className="p-2 space-y-2">
<div className="grid grid-cols-2 gap-0.5">
<Input
type="text"
placeholder="Event Code"
className="bg-[#333] border border-border/40 px-2 py-1.5 h-auto text-[11px] outline-none text-white focus:border-[#ff9800] rounded-none shadow-none"
/>
<div className="bg-[#333] border border-border/40 px-2 py-1.5 text-[11px] text-muted-foreground flex items-center justify-center">...</div>
</div>
{/* Tab switcher */}
<div className="flex items-center justify-between text-[11px] font-bold py-2.5 px-1 border-b border-border/10">
<div className="flex items-center gap-2">
<span className="text-[#ff9800] uppercase cursor-pointer">Betslip</span>
<span className="bg-[#ff9800] text-black px-1.5 rounded-full text-[10px] font-bold">0</span>
<Button
variant="ghost"
className="text-white uppercase flex items-center gap-1 ml-2 opacity-80 hover:opacity-100 p-0 h-auto font-bold text-[11px]"
>
<svg viewBox="0 0 24 24" className="size-3 fill-[#ff9800]"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>
Settings
</Button>
</div>
<span className="text-white uppercase cursor-pointer opacity-80 hover:opacity-100">Decimal</span>
</div>
<div className="py-10 px-4 text-center">
<p className="text-[11px] text-muted-foreground font-medium leading-relaxed">
No bet has been selected. To select a bet, please click on the respective odds
</p>
</div>
<ReloadTicket /> <ReloadTicket />
<CheckYourBet /> <CheckYourBet />
</div>
</aside> </aside>
); )
} }

View File

@ -0,0 +1,82 @@
"use client"
import Link from "next/link"
export function SiteFooter() {
return (
<footer className="bg-[#2a2a2a] text-white pt-12">
<div className="container mx-auto px-6 grid grid-cols-1 md:grid-cols-4 gap-12 text-center md:text-left">
{/* ABOUT */}
<div>
<h3 className="text-[12px] font-black uppercase mb-6 tracking-widest">ABOUT</h3>
<ul className="space-y-2 text-[11px] text-white/60 font-medium tracking-tight">
<li><Link href="/about" className="hover:text-primary transition-colors">About us</Link></li>
<li><Link href="/privacy" className="hover:text-primary transition-colors">Privacy Policy</Link></li>
<li><Link href="/responsible-gaming" className="hover:text-primary transition-colors">Responsible Gaming</Link></li>
<li><Link href="/contact" className="hover:text-primary transition-colors">Contact us</Link></li>
</ul>
</div>
{/* INFORMATION */}
<div>
<h3 className="text-[12px] font-black uppercase mb-6 tracking-widest">INFORMATION</h3>
<ul className="space-y-2 text-[11px] text-white/60 font-medium tracking-tight">
<li><Link href="/terms" className="hover:text-primary transition-colors">Terms & Conditions</Link></li>
<li><Link href="/faq" className="hover:text-primary transition-colors">FAQ</Link></li>
<li><Link href="/rules" className="hover:text-primary transition-colors">Betting Rules</Link></li>
<li><Link href="/info" className="hover:text-primary transition-colors">Betting Information</Link></li>
</ul>
</div>
{/* SPORTS */}
<div>
<h3 className="text-[12px] font-black uppercase mb-6 tracking-widest">SPORTS</h3>
<ul className="space-y-2 text-[11px] text-white/60 font-medium tracking-tight">
<li><Link href="/live" className="hover:text-primary transition-colors text-blue-400">Live betting</Link></li>
<li><Link href="/football" className="hover:text-primary transition-colors">Football</Link></li>
<li><Link href="/basketball" className="hover:text-primary transition-colors">Basketball</Link></li>
<li><Link href="/tennis" className="hover:text-primary transition-colors">Tennis</Link></li>
<li><Link href="/volleyball" className="hover:text-primary transition-colors">Volleyball</Link></li>
</ul>
</div>
{/* PLAY NOW */}
<div>
<h3 className="text-[12px] font-black uppercase mb-6 tracking-widest">PLAY NOW</h3>
<ul className="space-y-2 text-[11px] text-white/60 font-medium tracking-tight">
<li><Link href="/virtual" className="hover:text-primary transition-colors">Virtual</Link></li>
<li><Link href="/special-games" className="hover:text-primary transition-colors">Special Games</Link></li>
</ul>
</div>
</div>
{/* Logo Section */}
<div className="flex flex-col items-center justify-center py-16 border-t border-white/5 mt-12 bg-[#222]">
<div className="flex items-center bg-[#1a1a1a] px-5 py-2">
<div className="bg-[#852222] px-3 py-1 -skew-x-12">
<span className="text-3xl font-black text-white italic tracking-tighter skew-x-12 inline-block">HARIF</span>
</div>
<span className="text-3xl font-black text-[#ff9800] italic tracking-tighter ml-1">SPORT</span>
</div>
{/* Footer Links */}
<div className="flex flex-wrap items-center justify-center gap-6 mt-12 text-[11px] font-bold tracking-tight text-white/80">
<Link href="/affiliates" className="hover:text-primary uppercase transition-colors">Affiliates</Link>
<span className="size-1 bg-white/10 rounded-full" />
<Link href="/complaints" className="hover:text-primary uppercase transition-colors">Complaints</Link>
<span className="size-1 bg-white/10 rounded-full" />
<Link href="/deposits" className="hover:text-primary uppercase transition-colors">Deposits and Withdrawals</Link>
</div>
</div>
{/* Cookie Text */}
<div className="bg-[#1a1a1a] py-10 px-6 text-center">
<div className="container mx-auto max-w-5xl">
<p className="text-[10px] text-white/40 leading-relaxed font-medium uppercase tracking-tight">
By accessing, or continuing to use or browse this site, you consent to our use of certain cookies to improve your experience with us. We only use cookies that will enhance your experience and will not interfere with your privacy. Please look at our Cookie Policy for further informations on our use of the cookie and how you can disable it or manage it if you so choose.
</p>
</div>
</div>
</footer>
)
}

View File

@ -1,42 +1,293 @@
"use client"
import Link from "next/link" import Link from "next/link"
import { usePathname } from "next/navigation" import { usePathname } from "next/navigation"
import { useState, useEffect } from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
const navItems = [ const allNavItems = [
{ href: "/", label: "Sport Home" }, { href: "/", label: "SPORTS" },
{ href: "/live", label: "Live" }, { href: "/today", label: "TODAY" },
{ href: "/live", label: "LIVE" },
{ href: "/virtual", label: "VIRTUAL" },
{ href: "/special-games", label: "SPECIAL G." },
{ href: "/multi-hot-5", label: "MULTI HOT 5" },
{ href: "/poker", label: "POKER", isNew: true },
{ href: "/race", label: "RACE", isNew: true },
{ href: "/promo", label: "PROMO" },
{ href: "/aviator", label: "AVIATOR" },
] ]
export function SiteHeader() { const drawerLinks = [
{ href: "/", label: "All Sports" },
{ href: "/live", label: "Live Betting" },
{ href: "/virtual", label: "Virtual" },
{ href: "/special-games", label: "Special Games" },
{ href: "/poker", label: "Poker" },
{ href: "/race", label: "Race" },
{ href: "/promo", label: "Promotions" },
{ href: "/deposit", label: "Deposit" },
{ href: "/bonus", label: "Bonus" },
{ href: "/rules", label: "Betting Rules" },
{ href: "/terms", label: "Terms & Conditions" },
]
interface SiteHeaderProps {
onLoginClick?: () => void
onRegisterClick?: () => void
}
export function SiteHeader({ onLoginClick, onRegisterClick }: SiteHeaderProps) {
const pathname = usePathname() const pathname = usePathname()
const isLivePage = pathname === "/live"
const [time, setTime] = useState("")
const [prevPathname, setPrevPathname] = useState(pathname)
const [drawerOpen, setDrawerOpen] = useState(false)
// Adjust state during render when pathname changes
if (pathname !== prevPathname) {
setPrevPathname(pathname)
setDrawerOpen(false)
}
useEffect(() => {
const updateClock = () => {
setTime(new Date().toLocaleTimeString("en-GB", { hour12: false }))
}
const interval = setInterval(updateClock, 1000)
updateClock()
return () => clearInterval(interval)
}, [])
return ( return (
<header className="border-b bg-card"> <>
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-3"> <header className="bg-brand-bg sticky top-0 z-50">
<div className="text-lg font-semibold tracking-tight">Harifsport</div> {/* ===== DESKTOP: Top bar (hidden on mobile) ===== */}
<nav className="flex gap-2 text-sm"> <div className="hidden md:flex bg-brand-surface px-3 py-1 items-center justify-between text-[11px] text-white">
{navItems.map((item) => { <div className="flex items-center gap-4">
const isActive = <button className="flex items-center gap-1.5 hover:text-primary transition-colors">
item.href === "/" <img src="https://flagcdn.com/w20/gb.png" width="16" alt="English" className="rounded-sm" />
? pathname === "/" <span className="font-bold flex items-center gap-1">en <span className="text-[8px]"></span></span>
: pathname?.startsWith(item.href) </button>
<div className="flex items-center gap-2 font-bold">
<span className="text-[14px]">🕒</span>
<span className="tabular-nums">{time || "00:00:00"}</span>
</div>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-3">
<span className="text-white font-bold tracking-tight">+251 (0) Number</span>
<Input type="password" placeholder="Password" className="bg-brand-surface-light border-none px-2 py-0.5 w-32 text-[11px] h-7 rounded-none shadow-none text-white placeholder:text-gray-500" />
</div>
<div className="flex items-center gap-1">
<Button onClick={onLoginClick} className="bg-[#e67e22] text-white hover:bg-[#d35400] px-5 h-7 text-[11px] font-bold rounded-none uppercase">
Login
</Button>
<Button onClick={onRegisterClick} className="bg-[#d35400] text-white hover:bg-[#c0392b] px-5 h-7 text-[11px] font-bold rounded-none uppercase">
Sign Up
</Button>
</div>
</div>
</div>
{/* ===== MOBILE: Top bar (hidden on desktop) ===== */}
<div className="flex md:hidden items-center h-12 bg-brand-bg border-b border-white/5 px-2 gap-2">
{/* Hamburger */}
<button
onClick={() => setDrawerOpen(true)}
className="flex flex-col justify-center gap-[5px] p-2 shrink-0"
aria-label="Menu"
>
<span className="w-5 h-0.5 bg-white block" />
<span className="w-5 h-[2px] bg-white block" />
<span className="w-5 h-[2px] bg-white block" />
</button>
{/* Logo (centered, grows to fill) */}
<Link href="/" className="flex-1 flex items-center justify-center">
<div className="flex items-center">
<div className="bg-brand-accent px-2 py-0.5 -skew-x-12 flex items-center h-[28px]">
<span className="text-xl font-black text-white italic tracking-tighter skew-x-12 leading-none">HARIF</span>
</div>
<span className="text-xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">SPORT</span>
</div>
</Link>
{/* LOGIN / SIGN UP */}
<div className="flex items-center gap-1 shrink-0">
<button
onClick={onLoginClick}
className="bg-[#e67e22] text-white px-3 h-8 text-[11px] font-bold uppercase"
>
LOGIN
</button>
<button
onClick={onRegisterClick}
className="bg-[#d35400] text-white px-3 h-8 text-[11px] font-bold uppercase"
>
SIGN UP
</button>
</div>
</div>
{/* ===== DESKTOP: Main header bar ===== */}
<div className="hidden md:flex items-center px-0 bg-[#333] h-[60px]">
<Link href="/" className="flex items-center shrink-0">
<div className="flex items-center bg-brand-surface h-[60px] px-4">
<div className="bg-brand-accent px-3 py-1 -skew-x-12 flex items-center h-[34px]">
<span className="text-2xl font-black text-white italic tracking-tighter skew-x-12 inline-block leading-none">HARIF</span>
</div>
<span className="text-2xl font-black text-brand-primary italic tracking-tighter ml-1 leading-none">SPORT</span>
</div>
</Link>
<div className="flex items-center flex-1 justify-end px-4 h-full gap-0">
<Link href="/" className="flex items-center justify-center bg-brand-primary h-[60px] w-[50px] shrink-0 hover:bg-brand-primary-hover transition-colors">
<svg viewBox="0 0 24 24" className="size-6 fill-black"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
</Link>
<nav className="flex items-center text-[10.5px] font-bold h-full">
{allNavItems.slice(0, 5).map((item) => {
const isActive = pathname === item.href
return (
<Link key={item.href} href={item.href}
className={cn(
"px-4 flex items-center h-full transition-colors uppercase",
isActive ? (item.label === "LIVE" ? "bg-brand-primary text-black" : "text-primary bg-black/10") : "text-white hover:text-primary"
)}
>
{item.label}
</Link>
)
})}
</nav>
<nav className="flex items-center text-[10.5px] font-bold h-full">
{allNavItems.slice(5).map((item) => {
const isActive = pathname === item.href
return (
<Link key={item.href} href={item.href}
className={cn(
"px-4 flex flex-col items-center justify-center h-full transition-colors relative uppercase",
isActive ? "text-primary bg-black/10" : "text-white hover:text-primary"
)}
>
{item.isNew && (
<span className="absolute top-3 text-[7px] text-primary font-black tracking-tighter leading-none">NEW</span>
)}
<span className={cn(item.isNew && "mt-1.5")}>{item.label}</span>
</Link>
)
})}
</nav>
</div>
</div>
{/* ===== MOBILE: Horizontally scrollable nav tabs ===== */}
<div className="flex md:hidden overflow-x-auto scrollbar-none bg-brand-surface-light border-t border-white/5">
<Link href="/" className="flex-none px-4 h-9 flex items-center bg-brand-primary">
<svg viewBox="0 0 24 24" className="size-5 fill-black"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
</Link>
{allNavItems.map((item) => {
const isActive = pathname === item.href
return ( return (
<Link <Link
key={item.href} key={item.href}
href={item.href} href={item.href}
className={cn( className={cn(
"rounded-full px-4 py-1.5 transition-colors", "relative flex-none px-4 h-9 flex items-center text-[11px] font-bold uppercase whitespace-nowrap transition-colors",
"text-muted-foreground hover:text-foreground hover:bg-muted", isActive
isActive && "bg-primary text-primary-foreground" ? "text-brand-primary border-b-2 border-brand-primary"
: "text-white/70 hover:text-white"
)} )}
> >
{item.isNew && (
<span className="absolute top-1 right-1 text-[7px] text-primary font-black leading-none">NEW</span>
)}
{item.label} {item.label}
</Link> </Link>
); )
})}
</div>
{/* ===== MOBILE: Sport Category Icons Row ===== */}
<div className="flex md:hidden overflow-x-auto scrollbar-none bg-[#333] border-t border-white/5 py-2 px-2 gap-4">
{[
{ label: "Check Bet", icon: (active: boolean) => <svg viewBox="0 0 24 24" className="size-5 fill-white/80"><path d="M20 4H4c-1.11 0-2 .89-2 2v12c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z"/></svg>, count: 99 },
{ label: "Live", icon: (active: boolean) => <div className="size-2 bg-brand-live rounded-full animate-pulse" />, count: 1247 },
{ label: "Football", icon: (active: boolean) => <svg viewBox="0 0 24 24" className="size-5 fill-white/80"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6z"/></svg>, count: 95 },
{ label: "Tennis", icon: (active: boolean) => <svg viewBox="0 0 24 24" className="size-5 fill-white/80"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>, count: 384 },
{ label: "Basketball", icon: (active: boolean) => <svg viewBox="0 0 24 24" className="size-5 fill-white/80"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6z"/></svg>, count: 173 },
].map((item, idx) => (
<div key={idx} className="flex flex-col items-center gap-1 shrink-0 px-2 min-w-[60px] relative">
<span className="absolute -top-1 right-0 bg-white/20 text-white text-[9px] px-1 rounded-sm font-bold">{item.count}</span>
<div className="size-10 rounded-full bg-white/5 flex items-center justify-center border border-white/5">
{typeof item.icon === 'function' ? item.icon(false) : item.icon}
</div>
<span className="text-[9px] text-white/60 font-medium uppercase tracking-tighter">{item.label}</span>
</div>
))}
</div>
{/* ===== DESKTOP: Secondary sub-header ===== */}
<div className="hidden md:flex bg-brand-surface-light border-t border-white/5 h-8 px-3 items-center gap-6 text-[11px]">
{[
{ label: "Sport Home", href: "/" },
{ label: "General View", href: "/live", forceActive: isLivePage },
{ label: "Event View", href: "/live/event" },
].map((tab) => {
const isActive = tab.forceActive || pathname === tab.href
return (
<Link key={tab.label} href={tab.href}
className={cn(
"relative h-full flex items-center text-[10px] font-bold uppercase transition-colors tracking-tight px-1",
isActive ? "text-brand-primary" : "text-white/60 hover:text-white"
)}
>
{tab.label}
{isActive && tab.label !== "Sport Home" && (
<div className="absolute bottom-0 left-0 right-0 h-[2px] bg-brand-primary" />
)}
</Link>
)
})} })}
</nav>
</div> </div>
</header> </header>
{/* ===== MOBILE Drawer ===== */}
{drawerOpen && (
<div className="fixed inset-0 z-[9998] flex md:hidden">
{/* Backdrop */}
<div className="absolute inset-0 bg-black/60" onClick={() => setDrawerOpen(false)} />
{/* Drawer panel */}
<div className="relative w-72 bg-brand-bg h-full flex flex-col shadow-2xl animate-slide-in-left overflow-y-auto">
{/* Drawer header */}
<div className="flex items-center justify-between px-4 py-3 bg-brand-surface border-b border-white/10">
<div className="flex items-center">
<div className="bg-brand-accent px-2 py-0.5 -skew-x-12 flex items-center h-[24px]">
<span className="text-base font-black text-white italic tracking-tighter skew-x-12 leading-none">HARIF</span>
</div>
<span className="text-base font-black text-brand-primary italic tracking-tighter ml-1 leading-none">SPORT</span>
</div>
<button onClick={() => setDrawerOpen(false)} className="text-white/60 hover:text-white text-2xl leading-none">×</button>
</div>
{/* Nav links */}
<nav className="flex-1 py-2">
{drawerLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className={cn(
"flex items-center px-5 py-3 text-[13px] font-semibold border-b border-white/5 transition-colors",
pathname === link.href ? "text-brand-primary bg-white/5" : "text-white/80 hover:text-white hover:bg-white/5"
)}
>
{link.label}
</Link>
))}
</nav>
</div>
</div>
)}
</>
) )
} }

View File

@ -1,32 +1,164 @@
const sports = [ "use client"
"Soccer",
"Basketball", import { useState } from "react"
"Tennis", import Link from "next/link"
"Ice Hockey", import { popularLeagues } from "@/lib/mock-data"
"Volleyball", import { cn } from "@/lib/utils"
"Handball", import { Button } from "@/components/ui/button"
"Baseball",
"American Football", const sportCategories = [
"Cricket", { id: "football", name: "Football", icon: "⚽", count: 1412 },
"Rugby", { id: "tennis", name: "Tennis", icon: "🎾", count: 67 },
{ id: "basketball", name: "Basketball", icon: "🏀", count: 255 },
{ id: "ice-hockey", name: "Ice Hockey", icon: "🏒", count: 238 },
{ id: "mma", name: "MMA", icon: "🥊", count: 51 },
{ id: "handball", name: "Handball", icon: "🤾", count: 92 },
{ id: "darts", name: "Darts", icon: "🎯", count: 25 },
{ id: "snooker", name: "Snooker", icon: "🎱", count: 3 },
{ id: "cricket", name: "Cricket", icon: "🏏", count: 42 },
{ id: "dota2", name: "Dota 2", icon: "🎮", count: 2 },
{ id: "rugby", name: "Rugby", icon: "🏉", count: 41 },
{ id: "volleyball", name: "Volleyball", icon: "🏐", count: 69 },
] ]
export function SportsSidebar() { export function SportsSidebar() {
return ( const [activeSport, setActiveSport] = useState("football")
<aside className="hidden h-full w-52 shrink-0 border-r bg-sidebar px-3 py-3 text-sm text-sidebar-foreground lg:block">
<div className="mb-3 font-semibold uppercase tracking-wide text-xs text-muted-foreground">
Sports Menu
</div>
<ul className="space-y-1">
{sports.map((sport) => (
<li key={sport}>
<button className="w-full rounded px-2 py-1 text-left text-xs text-muted-foreground hover:bg-muted hover:text-foreground">
{sport}
</button>
</li>
))}
</ul>
</aside>
);
}
return (
<aside className="hidden h-full w-[240px] shrink-0 bg-brand-surface-light lg:block overflow-y-auto border-r border-border/40 scrollbar-hide">
{/* Sports Menu Header */}
<div className="bg-brand-surface px-3 py-2 text-[11px] font-black text-brand-primary uppercase tracking-tighter flex items-center justify-between border-b border-border/30">
Sports Menu
<svg viewBox="0 0 24 24" className="size-3.5 fill-current opacity-60"><path d="M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"/></svg>
</div>
{/* Top Leagues Header */}
<div className="bg-brand-surface-light px-3 py-2.5 text-[11px] font-black text-brand-primary uppercase text-center border-b border-border/20 flex items-center justify-center gap-2">
Top Leagues
</div>
{/* Popular Leagues */}
<div className="flex flex-col">
{popularLeagues.map((league) => (
<Link
key={league.id}
href={`/?league=${league.id}`}
className="w-full flex items-center justify-between px-3 py-2 text-left text-white/90 hover:bg-brand-surface transition-colors border-b border-border/10 group h-9"
>
<div className="flex items-center gap-2.5 min-w-0">
<div className="size-4 shrink-0 overflow-hidden rounded-sm flex items-center justify-center bg-white/10 group-hover:bg-white/20 transition-colors">
{league.logo ? (
<img src={league.logo} alt="" className="size-full object-contain" />
) : (
<span className="text-[10px] leading-none invert"></span>
)}
</div>
<span className="text-[10.5px] font-bold leading-tight truncate max-w-[150px]">{league.name}</span>
</div>
<div className="size-3.5 rounded-full border border-white/20 flex items-center justify-center group-hover:border-brand-primary transition-colors shrink-0">
<div className="size-1.5 bg-white/40 rounded-full group-hover:bg-brand-primary" />
</div>
</Link>
))}
</div>
{/* In-Play Strip */}
<Button className="w-full bg-[#004d40] text-[#00bfa5] hover:bg-[#003d33] border-none py-2.5 h-auto text-[11px] font-black uppercase rounded-none tracking-[2px]">
<span className="size-2 rounded-full bg-[#ff9800] mr-2 live-dot shadow-[0_0_8px_#ff9800]"></span>
IN-PLAY
</Button>
{/* Quick Filter Section */}
<div className="bg-brand-surface p-3 border-b border-border/30">
<span className="text-brand-primary text-[10.5px] uppercase font-black block mb-2 tracking-tight">Quick Filter</span>
<div className="grid grid-cols-6 gap-[1px]">
{["All", "Today", "3h", "6h", "9h", "12h"].map((t) => (
<button key={t} className={cn(
"text-[10px] py-1.5 font-bold transition-colors",
t === "All" ? "bg-[#333] text-white" : "bg-[#2a2a2a] text-white/50 hover:text-white"
)}>{t}</button>
))}
</div>
</div>
{/* Search Event Section */}
<div className="bg-brand-surface p-3 border-b border-border/40">
<span className="text-brand-primary text-[10.5px] uppercase font-black block mb-1 tracking-tight">Search Event</span>
<p className="text-[9px] text-white/30 mb-2 leading-tight font-medium uppercase">Insert the events name or at least one team in the form below</p>
<div className="flex flex-col gap-1.5">
<div className="relative">
<input type="text" className="bg-brand-surface-light border-none text-white text-[11px] px-3 py-2 w-full focus:ring-0 placeholder:text-white/20" placeholder="Search" />
</div>
<Button className="bg-brand-primary text-black hover:bg-brand-primary-hover py-2 h-auto rounded-none text-[11px] font-black uppercase tracking-wider">Search</Button>
</div>
</div>
{/* Sport categories */}
<div className="divide-y divide-border/10 bg-brand-surface-light">
{sportCategories.map((sport) => (
<button
key={sport.id}
onClick={() => setActiveSport(sport.id)}
className={cn(
"w-full flex items-center justify-between px-3 py-2 text-left transition-colors border-b border-border/10 h-9",
activeSport === sport.id
? "bg-brand-surface text-white"
: "text-white/70 hover:bg-brand-surface hover:text-white"
)}
>
<div className="flex items-center gap-3">
<span className="text-[12px] opacity-80 shrink-0">{sport.icon}</span>
<span className="text-[10.5px] font-bold tracking-tight">{sport.name}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-[10px] font-bold text-white/40">{sport.count}</span>
<span className="text-[11px] text-white/20"></span>
</div>
</button>
))}
</div>
{/* Bet Services */}
<div className="mt-2 text-[11px] font-bold text-brand-primary px-3 py-2 uppercase border-y border-border/20 bg-brand-surface">
Bet Services
</div>
<div className="grid grid-cols-3 gap-0 border-b border-border/10">
<Button variant="ghost" className="flex flex-col items-center justify-center py-4 h-auto rounded-none border-r border-border/10 hover:bg-brand-surface group">
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm3.3 14.71L11 12.41V7h2v4.59l3.71 3.71-1.42 1.41z"/></svg>
<span className="text-[9px] text-white font-medium">Live Score</span>
</Button>
<Button variant="ghost" className="flex flex-col items-center justify-center py-4 h-auto rounded-none border-r border-border/10 hover:bg-brand-surface group">
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M5 9.2h3V19H5zM10.6 5h2.8v14h-2.8zm5.6 8H19v6h-2.8z"/></svg>
<span className="text-[9px] text-white font-medium">Results</span>
</Button>
<Button variant="ghost" className="flex flex-col items-center justify-center py-4 h-auto rounded-none hover:bg-brand-surface group">
<svg viewBox="0 0 24 24" className="size-5 mb-1.5 fill-muted-foreground group-hover:fill-brand-primary transition-colors"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"/></svg>
<span className="text-[9px] text-white font-medium">Print Odds</span>
</Button>
</div>
<div className="p-2 space-y-1">
<Button className="w-full bg-[#004d40] text-[#00bfa5] hover:bg-[#003d33] border-none py-2 h-auto text-[11px] font-bold uppercase rounded-none tracking-widest">
<span className="size-2 rounded-full bg-brand-primary mr-2 live-dot"></span>
IN-PLAY
</Button>
<div className="bg-brand-surface p-3 border border-border/10">
<span className="text-brand-primary text-[10px] uppercase font-bold block mb-2">Quick Filter</span>
<div className="grid grid-cols-6 gap-0.5">
{["All", "Today", "3h", "6h", "9h", "12h"].map((t) => (
<button key={t} className="text-[9px] py-1 bg-brand-surface-light text-white/70 hover:bg-brand-surface transition-colors">{t}</button>
))}
</div>
</div>
<div className="bg-brand-surface p-3 border border-border/10">
<span className="text-brand-primary text-[10px] uppercase font-bold block mb-2">Search Event</span>
<p className="text-[9px] text-white/40 mb-2 leading-tight">Insert the events name or at least one team in the form below</p>
<div className="flex flex-col gap-1.5">
<input type="text" className="bg-brand-surface-light border-none text-white text-[10px] px-2 py-1.5 focus:ring-0" placeholder="Search" />
<Button className="bg-brand-surface text-white hover:bg-brand-bg py-1.5 h-auto rounded-none text-[10px] font-bold uppercase transition-colors">Search</Button>
</div>
</div>
</div>
</aside>
)
}

View File

@ -19,6 +19,11 @@ const buttonVariants = cva(
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
"hs-primary": "bg-[#ff9800] text-black font-bold hover:bg-[#ffa726] rounded-none",
"hs-secondary": "bg-[#333] text-white hover:bg-[#444] border border-border/20 rounded-none",
"hs-maroon": "bg-[#852222] text-white font-bold hover:bg-[#962d2d] rounded-none",
"hs-inplay": "bg-[#004242] text-[#ff9800] font-bold hover:bg-[#005252] border-y border-border/10 rounded-none",
"hs-nav": "bg-[#1a1a1a] text-[#ff9800] font-bold hover:bg-[#222] border-r border-border/10 rounded-none",
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: "h-9 px-4 py-2 has-[>svg]:px-3",

View File

@ -26,12 +26,14 @@ function Tabs({
} }
const tabsListVariants = cva( const tabsListVariants = cva(
"rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col", "rounded-lg p-[3px] group-data-[orientation=horizontal]/tabs:h-9 data-[variant=line]:rounded-none data-[variant=hs-home]:rounded-none data-[variant=hs-home]:bg-[#1a1a1a] data-[variant=hs-nav]:rounded-none data-[variant=hs-nav]:bg-[#1a1a1a] group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-muted", default: "bg-muted",
line: "gap-1 bg-transparent", line: "gap-1 bg-transparent",
"hs-home": "w-full gap-0 border border-border/20",
"hs-nav": "w-full gap-0 border border-border/20 overflow-x-auto justify-start",
}, },
}, },
defaultVariants: { defaultVariants: {
@ -66,8 +68,10 @@ function TabsTrigger({
className={cn( className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
"group-data-[variant=hs-home]/tabs-list:rounded-none group-data-[variant=hs-home]/tabs-list:h-12 group-data-[variant=hs-home]/tabs-list:text-[13px] group-data-[variant=hs-home]/tabs-list:font-extrabold group-data-[variant=hs-home]/tabs-list:uppercase group-data-[variant=hs-home]/tabs-list:data-[state=active]:bg-transparent group-data-[variant=hs-home]/tabs-list:data-[state=active]:text-white",
"group-data-[variant=hs-nav]/tabs-list:rounded-none group-data-[variant=hs-nav]/tabs-list:flex-col group-data-[variant=hs-nav]/tabs-list:min-w-[70px] group-data-[variant=hs-nav]/tabs-list:py-2 group-data-[variant=hs-nav]/tabs-list:gap-1 group-data-[variant=hs-nav]/tabs-list:border-r group-data-[variant=hs-nav]/tabs-list:border-border/10 group-data-[variant=hs-nav]/tabs-list:data-[state=active]:bg-[#222]",
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground", "data-[state=active]:bg-background dark:data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 data-[state=active]:text-foreground",
"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", "after:bg-[#ff9800] after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-0 group-data-[orientation=horizontal]/tabs:after:h-[3px] group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100 group-data-[variant=hs-home]/tabs-list:data-[state=active]:after:opacity-100",
className className
)} )}
{...props} {...props}

261
lib/mock-data.ts Normal file
View File

@ -0,0 +1,261 @@
export type Market = {
id: string;
label: string;
odds: number;
};
export type Event = {
id: string;
sport: string;
sportIcon: string;
league: string;
country: string;
homeTeam: string;
awayTeam: string;
time: string;
date: string; // Added date for grouping
isLive: boolean;
liveMinute?: number;
homeScore?: number;
awayScore?: number;
markets: Market[];
totalMarkets: number;
};
export type SportCategory = {
id: string;
name: string;
icon: string;
count: number;
};
export const sportCategories: SportCategory[] = [
{ id: "soccer", name: "Soccer", icon: "⚽", count: 142 },
{ id: "basketball", name: "Basketball", icon: "🏀", count: 38 },
{ id: "tennis", name: "Tennis", icon: "🎾", count: 26 },
{ id: "ice-hockey", name: "Ice Hockey", icon: "🏒", count: 18 },
{ id: "volleyball", name: "Volleyball", icon: "🏐", count: 14 },
{ id: "handball", name: "Handball", icon: "🤾", count: 12 },
{ id: "baseball", name: "Baseball", icon: "⚾", count: 8 },
{ id: "american-football", name: "American Football", icon: "🏈", count: 6 },
{ id: "cricket", name: "Cricket", icon: "🏏", count: 4 },
{ id: "rugby", name: "Rugby", icon: "🏉", count: 3 },
{ id: "boxing", name: "Boxing", icon: "🥊", count: 2 },
{ id: "esports", name: "E-Sports", icon: "🎮", count: 22 },
];
export const mockEvents: Event[] = [
{
id: "01682",
sport: "Soccer",
sportIcon: "⚽",
league: "LaLiga",
country: "Spain",
homeTeam: "Athletic Bilbao",
awayTeam: "Elche CF",
time: "11:00",
date: "Feb 20, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 1.64 },
{ id: "x", label: "x", odds: 3.91 },
{ id: "2", label: "2", odds: 5.45 },
{ id: "o25", label: "Over (2.5)", odds: 1.93 },
{ id: "u25", label: "Under (2.5)", odds: 1.88 },
{ id: "1x", label: "1X", odds: 1.15 },
{ id: "12", label: "12", odds: 1.24 },
{ id: "x2", label: "X2", odds: 2.13 },
{ id: "yes", label: "Yes", odds: 1.96 },
{ id: "no", label: "No", odds: 1.86 },
],
totalMarkets: 112,
},
{
id: "01570",
sport: "Soccer",
sportIcon: "⚽",
league: "LaLiga",
country: "Spain",
homeTeam: "Real Sociedad",
awayTeam: "Real Oviedo",
time: "04:00",
date: "Feb 21, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 1.52 },
{ id: "x", label: "x", odds: 4.41 },
{ id: "2", label: "2", odds: 6.91 },
{ id: "o25", label: "Over (2.5)", odds: 1.98 },
{ id: "u25", label: "Under (2.5)", odds: 1.87 },
{ id: "1x", label: "1X", odds: 1.11 },
{ id: "12", label: "12", odds: 1.21 },
{ id: "x2", label: "X2", odds: 2.43 },
{ id: "yes", label: "Yes", odds: 2.15 },
{ id: "no", label: "No", odds: 1.72 },
],
totalMarkets: 98,
},
{
id: "01541",
sport: "Soccer",
sportIcon: "⚽",
league: "LaLiga",
country: "Spain",
homeTeam: "Betis",
awayTeam: "Rayo Vallecano",
time: "06:15",
date: "Feb 21, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 1.92 },
{ id: "x", label: "x", odds: 3.55 },
{ id: "2", label: "2", odds: 4.10 },
{ id: "o25", label: "Over (2.5)", odds: 1.92 },
{ id: "u25", label: "Under (2.5)", odds: 1.88 },
{ id: "1x", label: "1X", odds: 1.23 },
{ id: "12", label: "12", odds: 1.28 },
{ id: "x2", label: "X2", odds: 1.80 },
{ id: "yes", label: "Yes", odds: 1.81 },
{ id: "no", label: "No", odds: 2.00 },
],
totalMarkets: 104,
},
{
id: "01605",
sport: "Soccer",
sportIcon: "⚽",
league: "LaLiga",
country: "Spain",
homeTeam: "Osasuna",
awayTeam: "Real Madrid",
time: "08:30",
date: "Feb 21, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 4.73 },
{ id: "x", label: "x", odds: 4.05 },
{ id: "2", label: "2", odds: 1.69 },
{ id: "o25", label: "Over (2.5)", odds: 1.67 },
{ id: "u25", label: "Under (2.5)", odds: 2.20 },
{ id: "1x", label: "1X", odds: 2.03 },
{ id: "12", label: "12", odds: 1.23 },
{ id: "x2", label: "X2", odds: 1.18 },
{ id: "yes", label: "Yes", odds: 1.70 },
{ id: "no", label: "No", odds: 2.15 },
],
totalMarkets: 128,
},
{
id: "01604",
sport: "Soccer",
sportIcon: "⚽",
league: "LaLiga",
country: "Spain",
homeTeam: "Atletico Madrid",
awayTeam: "Espanyol",
time: "11:00",
date: "Feb 21, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 1.51 },
{ id: "x", label: "x", odds: 4.57 },
{ id: "2", label: "2", odds: 6.81 },
{ id: "o25", label: "Over (2.5)", odds: 1.88 },
{ id: "u25", label: "Under (2.5)", odds: 1.96 },
{ id: "1x", label: "1X", odds: 1.11 },
{ id: "12", label: "12", odds: 1.20 },
{ id: "x2", label: "X2", odds: 2.43 },
{ id: "yes", label: "Yes", odds: 2.05 },
{ id: "no", label: "No", odds: 1.78 },
],
totalMarkets: 156,
},
{
id: "01672",
sport: "Soccer",
sportIcon: "⚽",
league: "Ligue 1",
country: "France",
homeTeam: "Brest",
awayTeam: "Marseille",
time: "10:45",
date: "Feb 20, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 3.91 },
{ id: "x", label: "x", odds: 3.80 },
{ id: "2", label: "2", odds: 1.97 },
{ id: "o25", label: "Over (2.5)", odds: 1.75 },
{ id: "u25", label: "Under (2.5)", odds: 2.15 },
{ id: "1x", label: "1X", odds: 1.80 },
{ id: "12", label: "12", odds: 1.27 },
{ id: "x2", label: "X2", odds: 1.26 },
{ id: "yes", label: "Yes", odds: 1.67 },
{ id: "no", label: "No", odds: 2.20 },
],
totalMarkets: 142,
},
{
id: "00241",
sport: "Soccer",
sportIcon: "⚽",
league: "LaLiga 2",
country: "Spain",
homeTeam: "AD Ceuta",
awayTeam: "Granada",
time: "10:30",
date: "Feb 20, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 2.55 },
{ id: "x", label: "x", odds: 3.20 },
{ id: "2", label: "2", odds: 2.75 },
{ id: "o25", label: "Over (2.5)", odds: 2.15 },
{ id: "u25", label: "Under (2.5)", odds: 1.66 },
{ id: "1x", label: "1X", odds: 1.40 },
{ id: "12", label: "12", odds: 1.32 },
{ id: "x2", label: "X2", odds: 1.45 },
{ id: "yes", label: "Yes", odds: 1.85 },
{ id: "no", label: "No", odds: 1.85 },
],
totalMarkets: 110,
},
{
id: "01124",
sport: "Soccer",
sportIcon: "⚽",
league: "Bundesliga",
country: "Germany",
homeTeam: "Mainz",
awayTeam: "Hamburger SV",
time: "10:30",
date: "Feb 20, 26",
isLive: false,
markets: [
{ id: "1", label: "1", odds: 2.11 },
{ id: "x", label: "x", odds: 3.40 },
{ id: "2", label: "2", odds: 3.58 },
{ id: "o25", label: "Over (2.5)", odds: 1.85 },
{ id: "u25", label: "Under (2.5)", odds: 1.96 },
{ id: "1x", label: "1X", odds: 1.28 },
{ id: "12", label: "12", odds: 1.30 },
{ id: "x2", label: "X2", odds: 1.66 },
{ id: "yes", label: "Yes", odds: 1.71 },
{ id: "no", label: "No", odds: 2.15 },
],
totalMarkets: 125,
},
];
export const popularLeagues = [
{ id: "ucl", name: "UEFA Champions League", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/b/bf/UEFA_Champions_League_logo_2.svg/300px-UEFA_Champions_League_logo_2.svg.png" },
{ id: "uel", name: "UEFA Europa League", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/0/0c/UEFA_Europa_League_logo_%282021%29.svg/300px-UEFA_Europa_League_logo_%282021%29.svg.png" },
{ id: "epl", name: "Premier League", country: "England", icon: "🏴󠁧󠁢󠁥󠁮󠁧󠁿", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/f/f2/Premier_League_Logo.svg/300px-Premier_League_Logo.svg.png" },
{ id: "laliga", name: "La Liga", country: "Spain", icon: "🇪<>", logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/LaLiga_logo_2023.svg/300px-LaLiga_logo_2023.svg.png" },
{ id: "laliga2", name: "LaLiga 2", country: "Spain", icon: "🇪🇸", logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/LaLiga_logo_2023.svg/300px-LaLiga_logo_2023.svg.png" },
{ id: "bundesliga", name: "Bundesliga", country: "Germany", icon: "🇩🇪", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/d/df/Bundesliga_logo_%282017%29.svg/300px-Bundesliga_logo_%282017%29.svg.png" },
{ id: "seriea", name: "Serie A", country: "Italy", icon: "🇮🇹", logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/Serie_A_logo_%282019%29.svg/300px-Serie_A_logo_%282019%29.svg.png" },
{ id: "ligue1", name: "Ligue 1", country: "France", icon: "🇫🇷", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/d/d4/Ligue_1_Uber_Eats_logo.svg/300px-Ligue_1_Uber_Eats_logo.svg.png" },
{ id: "ligue2", name: "Ligue 2", country: "France", icon: "<22><>", logo: "https://upload.wikimedia.org/wikipedia/en/thumb/9/91/Ligue_2_logo_2020.svg/300px-Ligue_2_logo_2020.svg.png" },
{ id: "eredivisie", name: "Eredivisie", country: "Netherlands", icon: "<22><>", logo: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Eredivisie_logo_2017.svg/300px-Eredivisie_logo_2017.svg.png" },
];

View File

@ -1,38 +1,75 @@
import { create } from "zustand"; import { create } from "zustand";
export type OddsFormat = "decimal"; export type OddsFormat = "decimal" | "fractional" | "american";
export type Bet = { export type Bet = {
id: string; id: string;
event: string; event: string;
league: string;
market: string; market: string;
selection: string; selection: string;
odds: number; odds: number;
stake?: number;
}; };
type BetslipState = { type BetslipState = {
bets: Bet[]; bets: Bet[];
oddsFormat: OddsFormat; oddsFormat: OddsFormat;
defaultStake: number;
addBet: (bet: Bet) => void; addBet: (bet: Bet) => void;
removeBet: (id: string) => void; removeBet: (id: string) => void;
clearBets: () => void; clearBets: () => void;
updateStake: (id: string, stake: number) => void;
setOddsFormat: (format: OddsFormat) => void;
setDefaultStake: (stake: number) => void;
getTotalOdds: () => number;
getTotalStake: () => number;
getPotentialWin: () => number;
}; };
export const useBetslipStore = create<BetslipState>((set) => ({ export const useBetslipStore = create<BetslipState>((set, get) => ({
bets: [], bets: [],
oddsFormat: "decimal", oddsFormat: "decimal",
defaultStake: 10,
addBet: (bet) => addBet: (bet) =>
set((state) => { set((state) => {
const exists = state.bets.some((b) => b.id === bet.id); const exists = state.bets.some((b) => b.id === bet.id);
if (exists) { if (exists) {
return state; // Toggle off if already selected
return { bets: state.bets.filter((b) => b.id !== bet.id) };
} }
return { bets: [...state.bets, bet] }; return { bets: [...state.bets, { ...bet, stake: state.defaultStake }] };
}), }),
removeBet: (id) => removeBet: (id) =>
set((state) => ({ set((state) => ({
bets: state.bets.filter((bet) => bet.id !== id), bets: state.bets.filter((bet) => bet.id !== id),
})), })),
clearBets: () => set({ bets: [] }), clearBets: () => set({ bets: [] }),
updateStake: (id, stake) =>
set((state) => ({
bets: state.bets.map((b) => (b.id === id ? { ...b, stake } : b)),
})),
setOddsFormat: (format) => set({ oddsFormat: format }),
setDefaultStake: (stake) => set({ defaultStake: stake }),
getTotalOdds: () => {
const bets = get().bets;
if (bets.length === 0) return 0;
return bets.reduce((acc, b) => acc * b.odds, 1);
},
getTotalStake: () => {
const bets = get().bets;
return bets.reduce((acc, b) => acc + (b.stake ?? 0), 0);
},
getPotentialWin: () => {
const bets = get().bets;
if (bets.length === 0) return 0;
if (bets.length === 1) {
const b = bets[0];
return (b.stake ?? 0) * b.odds;
}
// Accumulator: use first bet's stake
const stake = bets[0].stake ?? 0;
const totalOdds = bets.reduce((acc, b) => acc * b.odds, 1);
return stake * totalOdds;
},
})); }));

4
package-lock.json generated
View File

@ -1,11 +1,11 @@
{ {
"name": "harifsport-ui", "name": "fortune-play",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "harifsport-ui", "name": "fortune-play",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",