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() {
const [ticketId, setTicketId] = useState("")
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="text-sm text-foreground">Check ticket 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">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() {
const [method, setMethod] = useState("telebirr")
const [amount, setAmount] = useState("")
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-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
/* Brand Theme Parameters */
--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 {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.705 0.015 286.067);
--radius: 0rem;
/* Brand Colors (Black, Yellow, White theme) */
--brand-primary: #ff9800;
--brand-primary-hover: #e68900;
--brand-secondary: #ff9800;
/* Usually same as primary for now */
--brand-bg: #121212;
--brand-surface: #1a1a1a;
--brand-surface-light: #2a2a2a;
--brand-accent: #852222;
/* Maroon */
--brand-live: #ff3b3b;
/* Shadcn default mappings */
--background: var(--brand-bg);
--foreground: #ffffff;
--card: #1e1e1e;
--card-foreground: #ffffff;
--popover: #1e1e1e;
--popover-foreground: #ffffff;
--primary: var(--brand-primary);
--primary-foreground: #ffffff;
--secondary: #222222;
--secondary-foreground: #a0a0a0;
--muted: #1a1a1a;
--muted-foreground: #808080;
--accent: var(--brand-primary);
--accent-foreground: #121212;
--destructive: #ef4444;
--border: #2a2a2a;
--input: #222222;
--ring: var(--brand-primary);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--background: oklch(0.13 0.008 250);
--foreground: oklch(0.93 0.005 250);
--card: oklch(0.17 0.01 250);
--card-foreground: oklch(0.93 0.005 250);
--popover: oklch(0.17 0.01 250);
--popover-foreground: oklch(0.93 0.005 250);
--primary: oklch(0.55 0.18 145);
--primary-foreground: oklch(0.98 0 0);
--secondary: oklch(0.22 0.01 250);
--secondary-foreground: oklch(0.85 0.005 250);
--muted: oklch(0.2 0.008 250);
--muted-foreground: oklch(0.58 0.01 250);
--accent: oklch(0.55 0.18 145);
--accent-foreground: oklch(0.98 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--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);
--border: oklch(0.25 0.01 250);
--input: oklch(0.22 0.01 250);
--ring: oklch(0.55 0.18 145);
--sidebar: oklch(0.15 0.01 250);
--sidebar-foreground: oklch(0.9 0.005 250);
--sidebar-primary: oklch(0.55 0.18 145);
--sidebar-primary-foreground: oklch(0.98 0 0);
--sidebar-accent: oklch(0.22 0.01 250);
--sidebar-accent-foreground: oklch(0.93 0.005 250);
--sidebar-border: oklch(0.25 0.01 250);
--sidebar-ring: oklch(0.55 0.18 145);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@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() {
return (
<div className="text-sm text-foreground">History page placeholder</div>
)
const mockHistory = [
{ id: "BET001", event: "Arsenal vs Man City", selection: "Arsenal Win", odds: 2.80, stake: 100, status: "won", date: "2026-02-18" },
{ 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 { Geist, Geist_Mono } from "next/font/google"
import "./globals.css"
import { SiteHeader } from "@/components/layout/site-header"
import { SportsSidebar } from "@/components/layout/sports-sidebar"
import { RightPanel } from "@/components/layout/right-panel"
import LayoutClientWrapper from "@/components/layout/layout-client-wrapper"
const geistSans = Geist({
variable: "--font-geist-sans",
@ -16,8 +14,8 @@ const geistMono = Geist_Mono({
})
export const metadata: Metadata = {
title: "Harifsport",
description: "Harifsport-style sportsbook interface built with Next.js",
title: "Harifsport - Sports Betting",
description: "Harifsport sportsbook - Live betting, in-play events, and more",
}
export default function RootLayout({
@ -30,16 +28,8 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} bg-background text-foreground antialiased`}
>
<div className="flex min-h-screen flex-col bg-background">
<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>
<LayoutClientWrapper>{children}</LayoutClientWrapper>
</body>
</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() {
return <InPlayPage />
return (
<div className="space-y-4">
<LiveEventsList />
</div>
)
}

View File

@ -1,4 +1,131 @@
export default function LoginPage() {
return <div className="text-sm text-foreground">Login page placeholder</div>
}
"use client"
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() {
return <InPlayPage />
return <SportHome />
}

View File

@ -1,6 +1,50 @@
import Link from "next/link"
export default function ProfilePage() {
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() {
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() {
return <div className="text-sm text-foreground">Raffle page placeholder</div>
}
"use client"
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() {
const router = useRouter()
const [phone, setPhone] = useState("")
const [password, setPassword] = useState("")
const [repeatPassword, setRepeatPassword] = useState("")
const [ageConfirmed, setAgeConfirmed] = useState(false)
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() {
return <div className="text-sm text-foreground">Rules page placeholder</div>
}
const rules = [
{
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() {
return (
<div className="flex items-center gap-3 rounded-md border bg-card/60 px-3 py-2 text-[11px]">
<span className="font-medium uppercase tracking-wide text-muted-foreground">
Bet Services
<div className="flex items-center gap-0 bg-secondary rounded border border-border overflow-hidden text-[11px]">
<span className="px-3 py-2 font-bold uppercase text-muted-foreground text-[10px] tracking-wider border-r border-border">
Services
</span>
<Separator orientation="vertical" className="h-4" />
<div className="flex flex-wrap gap-2">
{services.map((service) => (
{services.map((service, i) => {
const Icon = service.icon
return (
<button
key={service}
className="text-[11px] text-primary underline-offset-2 hover:underline"
key={service.label}
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>
))}
</div>
)
})}
</div>
)
}

View File

@ -1,64 +1,187 @@
import { useBetslipStore } from "@/lib/store/betslip-store";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
"use client"
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() {
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 (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0">
<CardTitle className="text-sm font-semibold">
Betslip {bets.length}
</CardTitle>
<div className="bg-card rounded border border-border overflow-hidden">
{/* Header */}
<div className="bg-primary px-3 py-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<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 && (
<button
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>
)}
</CardHeader>
<CardContent className="space-y-3 text-xs">
</div>
{/* 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 ? (
<p className="text-muted-foreground">
No bet has been selected. To select a bet, please click on the
respective odds.
</p>
<div className="py-6 text-center">
<div className="text-2xl mb-2">🎯</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
No bets selected. Click on odds to add selections.
</p>
</div>
) : (
<div className="space-y-2">
{bets.map((bet) => (
<div
key={bet.id}
className="rounded border bg-card/40 p-2 text-xs"
>
<div className="flex items-center justify-between">
<div>
<div className="font-medium">{bet.event}</div>
<div className="text-[10px] text-muted-foreground">
{bet.market} {bet.selection}
<>
{/* Bet items */}
<div className="space-y-2">
{bets.map((bet) => (
<div key={bet.id} className="bg-secondary/50 rounded border border-border/50 p-2">
<div className="flex items-start justify-between gap-2 mb-2">
<div className="flex-1 min-w-0">
<div className="text-[11px] font-semibold text-foreground truncate">{bet.event}</div>
<div className="text-[10px] text-muted-foreground truncate">{bet.league}</div>
<div className="text-[10px] text-primary font-medium mt-0.5">
{bet.market}: <span className="text-foreground">{bet.selection}</span>
</div>
</div>
<div className="flex items-center gap-1.5 shrink-0">
<span className="text-sm font-bold text-primary">{bet.odds.toFixed(2)}</span>
<button
onClick={() => removeBet(bet.id)}
className="text-muted-foreground hover:text-destructive transition-colors"
>
<X className="size-3.5" />
</button>
</div>
</div>
<div className="text-right">
<div className="font-semibold">{bet.odds.toFixed(2)}</div>
<button
onClick={() => removeBet(bet.id)}
className="text-[10px] text-destructive hover:underline"
>
Remove
</button>
{/* 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 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>
))}
</div>
{/* Potential win */}
<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>
))}
</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>
))}
<Button className="w-full text-xs" size="sm">
Place bet
</Button>
</div>
)}
</CardContent>
</Card>
);
}
)}
{/* 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"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
"use client"
import { useState } from "react"
export function CheckYourBet() {
const [betId, setBetId] = useState("")
const [result, setResult] = useState<null | "pending" | "won" | "lost">(null)
const handleCheck = () => {
if (!betId.trim()) return
// Mock result
const results = ["pending", "won", "lost"] as const
setResult(results[Math.floor(Math.random() * results.length)])
}
return (
<Card>
<CardHeader>
<CardTitle className="text-sm">Check your bet</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-xs">
<div className="text-[11px] text-muted-foreground">Your bet ID</div>
<div className="flex gap-2">
<Input
placeholder="Bet ID"
className="h-8 text-xs placeholder:text-[11px]"
<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 size="sm" className="text-xs">
Check
</Button>
<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>
</CardContent>
</Card>
{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"
import { Card, CardContent } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
"use client"
import { useState, useEffect } from "react"
import { useSearchParams } from "next/navigation"
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 = [
{
id: "1",
sport: "Soccer",
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()
function OddsButton({ odds, onClick, isSelected }: {
odds: number
onClick: () => void
isSelected: boolean
}) {
return (
<Card className="flex-1">
<CardContent className="px-0 pb-4 pt-3">
<ScrollArea className="h-[360px]">
<div className="space-y-2 px-3 pb-2">
{mockEvents.map((event) => (
<div
key={event.id}
className="rounded-md border bg-card/50 p-2 text-xs"
>
<div className="flex items-center justify-between">
<div>
<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>
<button
onClick={onClick}
className={cn(
"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",
isSelected && "bg-[#ff9800] text-black font-bold border-none"
)}
>
<span className={cn("font-bold tracking-tighter", isSelected ? "text-black" : "text-[#ff9800]")}>{odds.toFixed(2)}</span>
</button>
)
}
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() {
return (
<div className="flex items-center justify-between border-b pb-2">
<h1 className="text-sm font-semibold uppercase tracking-wide text-foreground">
IN-PLAY
</h1>
<span className="text-[11px] text-muted-foreground">Quick Filter</span>
<div className="flex items-center justify-between border-b border-border pb-2">
<div className="flex items-center gap-2">
<h1 className="text-sm font-black uppercase tracking-wide text-foreground">
IN-PLAY
</h1>
<span className="text-[10px] text-muted-foreground">/ Today&apos;s Events</span>
</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 (
<Tabs defaultValue="All" className="w-full">
<TabsList className="h-8">
{filters.map((filter) => (
<TabsTrigger key={filter} value={filter} className="px-3 text-xs">
{filter}
</TabsTrigger>
))}
</TabsList>
</Tabs>
<div className="flex items-center gap-1 overflow-x-auto pb-0.5">
{filters.map((filter) => (
<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 === "Live" && (
<span className="ml-1 live-dot inline-block size-1.5 rounded-full bg-[var(--hs-live-red)] align-middle" />
)}
</button>
))}
</div>
)
}

View File

@ -1,28 +1,27 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
"use client"
import { useState } from "react"
export function ReloadTicket() {
const [code, setCode] = useState("")
return (
<Card>
<CardHeader>
<CardTitle className="text-sm">Reload Ticket</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-xs">
<div className="text-[11px] text-muted-foreground">
Insert the code to load
</div>
<div className="flex gap-2">
<Input
placeholder="Ticket code"
className="h-8 text-xs placeholder:text-[11px]"
<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]">Reload Ticket</h3>
<p className="text-[11px] text-white">Insert the code to load</p>
<div className="flex gap-1">
<input
type="text"
value={code}
onChange={(e) => setCode(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"
/>
<Button size="sm" className="text-xs">
Reload
</Button>
<button 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>
</CardContent>
</Card>
</div>
</div>
)
}

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 (
<div className="space-y-1 rounded-md border bg-card/60 p-3">
<div className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
Search Event
</div>
<p className="text-[11px] text-muted-foreground">
Insert the events name or at least one team in the form below
</p>
<Input
placeholder="Search by event or team"
className="h-8 text-xs placeholder:text-[11px]"
<div className="relative">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Search by event, team or league..."
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"
/>
</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 { ReloadTicket } from "@/components/betting/reload-ticket"
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() {
const [activeTab, setActiveTab] = useState<"betslip" | "myBets">("betslip")
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">
<Betslip />
<Card>
<CardHeader>
<CardTitle className="text-sm">Settings</CardTitle>
</CardHeader>
<CardContent className="text-xs text-muted-foreground">
Decimal odds (more settings coming soon)
</CardContent>
</Card>
<ReloadTicket />
<CheckYourBet />
<aside className="hidden lg:flex w-[280px] shrink-0 flex-col gap-0 border-l border-border/30 bg-[#222]">
{/* Search Header */}
<div className="p-3 bg-[#1a1a1a] flex items-center justify-between border-b border-border/20">
<div className="flex items-center gap-2 text-[12px] font-bold text-white uppercase">
Fast Bet <span className="text-[#ff9800]">QBET</span>
<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>
</div>
</div>
<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 />
<CheckYourBet />
</div>
</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 { usePathname } from "next/navigation"
import { useState, useEffect } from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
const navItems = [
{ href: "/", label: "Sport Home" },
{ href: "/live", label: "Live" },
const allNavItems = [
{ href: "/", label: "SPORTS" },
{ 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 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 (
<header className="border-b bg-card">
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-3">
<div className="text-lg font-semibold tracking-tight">Harifsport</div>
<nav className="flex gap-2 text-sm">
{navItems.map((item) => {
const isActive =
item.href === "/"
? pathname === "/"
: pathname?.startsWith(item.href)
<>
<header className="bg-brand-bg sticky top-0 z-50">
{/* ===== DESKTOP: Top bar (hidden on mobile) ===== */}
<div className="hidden md:flex bg-brand-surface px-3 py-1 items-center justify-between text-[11px] text-white">
<div className="flex items-center gap-4">
<button className="flex items-center gap-1.5 hover:text-primary transition-colors">
<img src="https://flagcdn.com/w20/gb.png" width="16" alt="English" className="rounded-sm" />
<span className="font-bold flex items-center gap-1">en <span className="text-[8px]"></span></span>
</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 (
<Link
key={item.href}
href={item.href}
className={cn(
"rounded-full px-4 py-1.5 transition-colors",
"text-muted-foreground hover:text-foreground hover:bg-muted",
isActive && "bg-primary text-primary-foreground"
"relative flex-none px-4 h-9 flex items-center text-[11px] font-bold uppercase whitespace-nowrap transition-colors",
isActive
? "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}
</Link>
);
)
})}
</nav>
</div>
</header>
</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>
)
})}
</div>
</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 = [
"Soccer",
"Basketball",
"Tennis",
"Ice Hockey",
"Volleyball",
"Handball",
"Baseball",
"American Football",
"Cricket",
"Rugby",
"use client"
import { useState } from "react"
import Link from "next/link"
import { popularLeagues } from "@/lib/mock-data"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
const sportCategories = [
{ id: "football", name: "Football", icon: "⚽", count: 1412 },
{ 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() {
return (
<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>
);
}
const [activeSport, setActiveSport] = useState("football")
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:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
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: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",

View File

@ -26,12 +26,14 @@ function Tabs({
}
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: {
variant: {
default: "bg-muted",
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: {
@ -66,8 +68,10 @@ function TabsTrigger({
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",
"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",
"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
)}
{...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";
export type OddsFormat = "decimal";
export type OddsFormat = "decimal" | "fractional" | "american";
export type Bet = {
id: string;
event: string;
league: string;
market: string;
selection: string;
odds: number;
stake?: number;
};
type BetslipState = {
bets: Bet[];
oddsFormat: OddsFormat;
defaultStake: number;
addBet: (bet: Bet) => void;
removeBet: (id: string) => 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: [],
oddsFormat: "decimal",
defaultStake: 10,
addBet: (bet) =>
set((state) => {
const exists = state.bets.some((b) => b.id === bet.id);
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) =>
set((state) => ({
bets: state.bets.filter((bet) => bet.id !== id),
})),
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",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "harifsport-ui",
"name": "fortune-play",
"version": "0.1.0",
"dependencies": {
"class-variance-authority": "^0.7.1",