show date/time for the current timezone

This commit is contained in:
2025-04-25 20:49:44 +02:00
parent ef67276b9f
commit bd4ddbc0d4
5 changed files with 172 additions and 202 deletions

View File

@@ -0,0 +1,153 @@
import { PlayerAvatar } from '@/app/(home)/major-league-balatro/_components/player-avatar'
import { players } from '@/app/(home)/major-league-balatro/_constants/players'
import type { Match } from '@/app/(home)/major-league-balatro/types'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/mobile-tooltip'
import { SiTwitch, SiYoutube } from '@icons-pack/react-simple-icons'
import { TvMinimalPlay } from 'lucide-react'
import { useFormatter } from 'next-intl'
import Link from 'next/link'
import type React from 'react'
type MatchCardProps = {
match: Match
}
export const MatchCard = ({ match }: MatchCardProps) => {
const formatter = useFormatter()
const { player1Id, player2Id, datetime, completed, vod1, vod2 } = match
const date = formatter.dateTime(datetime, {
month: 'long',
day: 'numeric',
})
const time = formatter.dateTime(datetime, {
timeStyle: 'short',
})
const player1 = players[player1Id]
const player2 = players[player2Id]
if (!player1) {
throw new Error(`Player ${player1Id} not found`)
}
if (!player2) {
throw new Error(`Player ${player2Id} not found`)
}
return (
<Card className='overflow-hidden'>
<div className='flex flex-col sm:flex-row'>
<div className='flex items-center justify-center p-4 sm:w-1/4'>
<div className='text-muted-foreground text-sm underline decoration-dashed underline-offset-4'>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
{date} {time}
</span>
</TooltipTrigger>
<TooltipContent align={'center'} sideOffset={5}>
<div>Date and time are shown for your current timezone.</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<div className='p-4 sm:w-3/4'>
<div className='flex flex-col justify-between gap-4 sm:flex-row sm:items-center'>
<div className='flex items-center gap-3'>
<div className='-space-x-4 flex'>
<PlayerAvatar playerName={player1.name} img={player1.picture} />
<PlayerAvatar playerName={player2.name} img={player2.picture} />
</div>
<div>
<h3 className='font-bold text-lg'>
{player1.name} vs {player2.name}
</h3>
</div>
</div>
<div className='flex gap-2'>
{completed ? (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm' className='gap-1'>
<TvMinimalPlay className='size-4' />
Watch
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{vod1 && (
<DropdownMenuItem asChild>
<Link
href={vod1}
target='_blank'
rel='noopener noreferrer'
>
<SiYoutube className='h-4 w-4' />
{player1.name}
</Link>
</DropdownMenuItem>
)}
{vod2 && (
<DropdownMenuItem asChild>
<Link
href={vod2}
target='_blank'
rel='noopener noreferrer'
>
<SiYoutube className='h-4 w-4' />
{player2.name}
</Link>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm' className='gap-1'>
<SiTwitch className='size-4' />
Watch Live
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem asChild>
<Link
href={`https://twitch.tv/${player1.socials.twitch}`}
target='_blank'
rel='noopener noreferrer'
>
{player1.name}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`https://twitch.tv/${player2.socials.twitch}`}
target='_blank'
rel='noopener noreferrer'
>
{player2.name}
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
</div>
</div>
</Card>
)
}

View File

@@ -1,5 +1,7 @@
import { PlayerAvatar } from '@/app/(home)/major-league-balatro/_components/player-avatar'
import { players } from '@/app/(home)/major-league-balatro/_constants/players'
import { Button } from '@/components/ui/button'
import { ExternalLink, Twitch, Users, Youtube } from 'lucide-react'
import { SiTwitch, SiYoutube } from '@icons-pack/react-simple-icons'
import Link from 'next/link'
export function Organizer() {
@@ -14,7 +16,11 @@ export function Organizer() {
<div className='mx-auto mt-8 flex max-w-3xl flex-col items-center gap-8 md:flex-row'>
<div className='relative h-32 w-32 flex-shrink-0 rounded-full bg-muted'>
<div className='absolute inset-0 flex items-center justify-center'>
<Users className='h-16 w-16 text-muted-foreground/50' />
<PlayerAvatar
className={'size-32'}
img={players.zainotv.picture}
playerName={players.zainotv.picture}
/>
</div>
</div>
<div>
@@ -31,7 +37,7 @@ export function Organizer() {
rel='noopener noreferrer'
>
<Button variant='outline' size='sm' className='gap-2'>
<Twitch className='h-4 w-4' />
<SiTwitch className='h-4 w-4' />
Twitch
</Button>
</Link>
@@ -41,7 +47,7 @@ export function Organizer() {
rel='noopener noreferrer'
>
<Button variant='outline' size='sm' className='gap-2'>
<Youtube className='h-4 w-4' />
<SiYoutube className='h-4 w-4' />
YouTube
</Button>
</Link>

View File

@@ -1,28 +1,9 @@
import { players } from '@/app/(home)/major-league-balatro/_constants/players'
import { MatchCard } from '@/app/(home)/major-league-balatro/_components/match-card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { cn } from '@/lib/utils'
import { SiTwitch, SiYoutube } from '@icons-pack/react-simple-icons'
import {
Calendar,
Camera,
Clock,
TvMinimalPlay,
Twitch,
Youtube,
} from 'lucide-react'
import Link from 'next/link'
import { useMemo } from 'react'
import type { BadgeProps, Match, WeekConfig } from '../types'
import { PlayerAvatar } from './player-avatar'
const WEEK_CONFIG: Record<string | number, WeekConfig> = {
1: {
@@ -110,181 +91,6 @@ type MatchDivisionProps = {
division: 'Blue' | 'Red'
}
const MatchDivision = ({ division }: MatchDivisionProps) => {
const bgColor = division === 'Blue' ? 'bg-blue-950/20' : 'bg-red-950/20'
const textColor =
division === 'Blue'
? 'border-blue-500 text-blue-500'
: 'border-red-500 text-red-500'
return (
<div className={`flex items-center justify-center p-4 sm:w-1/4 ${bgColor}`}>
<Badge variant='outline' className={textColor}>
{division} Division
</Badge>
</div>
)
}
type MatchDateTimeProps = {
date: string
time: string
}
const MatchDateTime = ({ date, time }: MatchDateTimeProps) => (
<div className='flex items-center gap-2 text-muted-foreground text-sm'>
<Calendar className='h-4 w-4' />
<span>{date}</span>
<Clock className='ml-2 h-4 w-4' />
<span>{time}</span>
</div>
)
type VodButtonProps = {
url: string
player: string
}
const VodButton = ({ url, player }: VodButtonProps) => (
<Link href={url} target='_blank' rel='noopener noreferrer'>
<Button variant='outline' size='sm' className='gap-1'>
<Youtube className='h-4 w-4' />
{player}
</Button>
</Link>
)
type LiveButtonProps = {
username: string
}
const LiveButton = ({ username }: LiveButtonProps) => (
<Link
href={`https://twitch.tv/${username.toLowerCase()}`}
target='_blank'
rel='noopener noreferrer'
>
<Button variant='outline' size='sm' className='gap-1'>
<Twitch className='h-4 w-4' />
Watch Live
</Button>
</Link>
)
type MatchCardProps = {
match: Match
}
const MatchCard = ({ match }: MatchCardProps) => {
const { player1Id, player2Id, date, time, completed, vod1, vod2 } = match
const player1 = players[player1Id]
const player2 = players[player2Id]
if (!player1) {
throw new Error(`Player ${player1Id} not found`)
}
if (!player2) {
throw new Error(`Player ${player2Id} not found`)
}
return (
<Card className='overflow-hidden'>
<div className='flex flex-col sm:flex-row'>
<div className='flex items-center justify-center p-4 sm:w-1/4'>
<div className='text-muted-foreground text-sm'>
{date} {time}
</div>
</div>
<div className='p-4 sm:w-3/4'>
<div className='flex flex-col justify-between gap-4 sm:flex-row sm:items-center'>
<div className='flex items-center gap-3'>
<div className='-space-x-4 flex'>
<PlayerAvatar playerName={player1.name} img={player1.picture} />
<PlayerAvatar playerName={player2.name} img={player2.picture} />
</div>
<div>
<h3 className='font-bold text-lg'>
{player1.name} vs {player2.name}
</h3>
</div>
</div>
<div className='flex gap-2'>
{completed ? (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm' className='gap-1'>
<TvMinimalPlay className='size-4' />
Watch
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{vod1 && (
<DropdownMenuItem asChild>
<Link
href={vod1}
target='_blank'
rel='noopener noreferrer'
>
<SiYoutube className='h-4 w-4' />
{player1.name}
</Link>
</DropdownMenuItem>
)}
{vod2 && (
<DropdownMenuItem asChild>
<Link
href={vod2}
target='_blank'
rel='noopener noreferrer'
>
<SiYoutube className='h-4 w-4' />
{player2.name}
</Link>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</>
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm' className='gap-1'>
<SiTwitch className='size-4' />
Watch Live
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem asChild>
<Link
href={`https://twitch.tv/${player1.socials.twitch}`}
target='_blank'
rel='noopener noreferrer'
>
{player1.name}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
href={`https://twitch.tv/${player2.socials.twitch}`}
target='_blank'
rel='noopener noreferrer'
>
{player2.name}
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
</div>
</div>
</Card>
)
}
type WeekTabProps = {
week: string | number
matches: Match[]
@@ -339,7 +145,7 @@ export function MlbSchedule({ matches }: MlbScheduleProps) {
sortedMatches.find((match) => new Date(match.datetime) > now)?.week || 1
)
}, [sortedMatches])
console.log(currentWeek)
return (
<section id='schedule' className='container py-8 md:py-12'>
<div className='mx-auto flex max-w-[58rem] flex-col items-center space-y-4 text-center'>

View File

@@ -1,6 +1,6 @@
import type { Player } from '../types'
export const players: Record<string, Player> = {
export const players = {
roffle: {
id: 'roffle',
name: 'Roffle',
@@ -120,4 +120,4 @@ export const players: Record<string, Player> = {
youtube: 'seadubbs11',
},
},
}
} satisfies Record<string, Player>

View File

@@ -4,6 +4,7 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar'
import type * as React from 'react'
import { cn } from '@/lib/utils'
import { CDN_URL } from '@/shared/constants'
function Avatar({
className,
@@ -23,12 +24,16 @@ function Avatar({
function AvatarImage({
className,
src,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
const isDev = process.env.NODE_ENV === 'development'
return (
<AvatarPrimitive.Image
data-slot='avatar-image'
className={cn('aspect-square size-full object-cover!', className)}
src={isDev ? src : `${CDN_URL}${src}`}
{...props}
/>
)