Skip to content

Commit d5b3df0

Browse files
committed
feat: tabs component for bucnation page
1 parent ef5090c commit d5b3df0

File tree

4 files changed

+267
-129
lines changed

4 files changed

+267
-129
lines changed

app/components/ui/tabs.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as TabsPrimitive from "@radix-ui/react-tabs"
2+
import * as React from "react"
3+
4+
import { cn } from "#app/utils/misc.tsx"
5+
6+
const Tabs = TabsPrimitive.Root
7+
8+
const TabsList = React.forwardRef<
9+
React.ElementRef<typeof TabsPrimitive.List>,
10+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
11+
>(({ className, ...props }, ref) => (
12+
<TabsPrimitive.List
13+
ref={ref}
14+
className={cn(
15+
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
16+
className
17+
)}
18+
{...props}
19+
/>
20+
))
21+
TabsList.displayName = TabsPrimitive.List.displayName
22+
23+
const TabsTrigger = React.forwardRef<
24+
React.ElementRef<typeof TabsPrimitive.Trigger>,
25+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
26+
>(({ className, ...props }, ref) => (
27+
<TabsPrimitive.Trigger
28+
ref={ref}
29+
className={cn(
30+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
31+
className
32+
)}
33+
{...props}
34+
/>
35+
))
36+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37+
38+
const TabsContent = React.forwardRef<
39+
React.ElementRef<typeof TabsPrimitive.Content>,
40+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
41+
>(({ className, ...props }, ref) => (
42+
<TabsPrimitive.Content
43+
ref={ref}
44+
className={cn(
45+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
46+
className
47+
)}
48+
{...props}
49+
/>
50+
))
51+
TabsContent.displayName = TabsPrimitive.Content.displayName
52+
53+
export { Tabs, TabsList, TabsTrigger, TabsContent }

app/routes/bucnation.tsx

+182-129
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,205 @@
1-
import { type HeadersFunction, type LoaderFunctionArgs, type ActionFunctionArgs , json } from "@remix-run/node";
2-
import { type MetaFunction, useLoaderData, isRouteErrorResponse, useRouteError ,type ShouldRevalidateFunction } from "@remix-run/react";
3-
import { Avatar, AvatarFallback, AvatarImage } from "#app/components/ui/avatar"
4-
import { Button } from "#app/components/ui/button";
5-
import { Card } from "#app/components/ui/card";
6-
1+
import {
2+
type HeadersFunction,
3+
type LoaderFunctionArgs,
4+
type ActionFunctionArgs,
5+
json,
6+
} from '@remix-run/node'
7+
import {
8+
type MetaFunction,
9+
useLoaderData,
10+
isRouteErrorResponse,
11+
useRouteError,
12+
type ShouldRevalidateFunction,
13+
} from '@remix-run/react'
14+
import { Avatar, AvatarFallback, AvatarImage } from '#app/components/ui/avatar'
15+
import { Card } from '#app/components/ui/card'
16+
import {
17+
Tabs,
18+
TabsContent,
19+
TabsList,
20+
TabsTrigger,
21+
} from '#app/components/ui/tabs'
722

823
interface VCActivityType {
9-
id: number
10-
activeFlag: string
11-
status: string
12-
playerFirstName: string
13-
playerLastName: string
14-
playerPosition: string
15-
playerWeightLbs: number
16-
playerHeightInches: number
17-
playerEligibilityYear: string
24+
id: number
25+
activeFlag: string
26+
status: string
27+
playerFirstName: string
28+
playerLastName: string
29+
playerPosition: string
30+
playerWeightLbs: number
31+
playerHeightInches: number
32+
playerEligibilityYear: string
1833
}
1934

2035
export const meta: MetaFunction = ({ data }) => [
36+
{ title: data ? 'lucasb.dev | BucNation' : 'Error | lucasb.dev' },
37+
{
38+
name: 'description',
39+
content: `A page dedicated to East Tennessee State Basketball`,
40+
},
41+
]
2142

22-
{ title: data ? 'lucasb.dev | BucNation' : 'Error | lucasb.dev' },
23-
{
24-
name: 'description',
25-
content: `A page dedicated to East Tennessee State Basketball`,
26-
},
27-
28-
];
29-
30-
export const headers: HeadersFunction = () => (
31-
{
32-
// your headers here
33-
}
34-
);
43+
export const headers: HeadersFunction = () => ({
44+
// your headers here
45+
})
3546

3647
export const loader = async ({ request }: LoaderFunctionArgs) => {
37-
const reqHeaders = {
38-
Pb: "MV7mOE51zp9clOm7",
39-
"User-Agent":
40-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
41-
};
48+
const reqHeaders = {
49+
Pb: 'iKi6oAQ2TmqZBqDc',
50+
'User-Agent':
51+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
52+
}
4253

43-
const response = await fetch("https://api.verbalcommits.com/vc/schools/chart/etsu", {
44-
headers: reqHeaders,
45-
});
46-
const data = await response.json() as VCActivityType[];
47-
const filteredResults = data.filter((d) => d.status === 'ENROLLMENT');
54+
const response = await fetch(
55+
'https://api.verbalcommits.com/vc/schools/chart/etsu',
56+
{
57+
headers: reqHeaders,
58+
},
59+
)
60+
const data = (await response.json()) as VCActivityType[]
61+
const filteredResults = data.filter(d => d.status === 'ENROLLMENT')
4862

49-
return json({ roster: filteredResults });
50-
};
63+
return json({ roster: filteredResults })
64+
}
5165

5266
export const action = async ({ request }: ActionFunctionArgs) => {
53-
return null;
54-
};
67+
return null
68+
}
5569

5670
const heightFromInches = (total: number) => {
57-
const feet = Math.floor(total / 12);
58-
const inches = total % 12;
59-
return `${feet}'${inches}"`;
71+
const feet = Math.floor(total / 12)
72+
const inches = total % 12
73+
return `${feet}'${inches}"`
6074
}
6175

62-
export default function BucNation(){
63-
const data = useLoaderData<typeof loader>();
76+
const games = [
77+
{
78+
opponent: 'TBD',
79+
date: 'November 18, 2024',
80+
score: {
81+
home: 0,
82+
away: 0,
83+
},
84+
image: 'https://generated.vusercontent.net/placeholder.svg',
85+
},
86+
];
87+
88+
export default function BucNation() {
89+
const data = useLoaderData<typeof loader>()
6490

65-
/* const games = [
66-
{
67-
opponent: "Los Angeles Lakers",
68-
date: "October 18, 2023",
69-
score: {
70-
home: 112,
71-
away: 104,
72-
},
73-
image: "/opponent1.jpg",
74-
}]; */
91+
return (
92+
<Tabs
93+
defaultValue="roster"
94+
className="mx-auto w-full max-w-6xl px-4 py-8 md:px-6"
95+
>
96+
<header className="mb-8 flex flex-col sm:flex-row items-center justify-between">
97+
<div className="flex items-center gap-4">
98+
<img
99+
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/East_Tennessee_State_Buccaneers_logo.svg/400px-East_Tennessee_State_Buccaneers_logo.svg.png"
100+
alt="ETSU Team Logo"
101+
width={40}
102+
height={40}
103+
className="h-10 w-10"
104+
/>
105+
<h1 className="text-2xl font-bold">
106+
East Tennessee State{' '}
107+
<span className="text-yellow-500">Buccaneers</span> Basketball
108+
</h1>
109+
</div>
110+
<TabsList className='mt-4 sm:mt-0'>
111+
<TabsTrigger value="roster">Roster</TabsTrigger>
112+
<TabsTrigger value="schedule">Schedule</TabsTrigger>
113+
<TabsTrigger value="stats">Stats</TabsTrigger>
114+
</TabsList>
115+
</header>
75116

76-
return (
77-
<div className="w-full max-w-6xl mx-auto py-8 px-4 md:px-6">
78-
<header className="flex items-center justify-between mb-8">
79-
<div className="flex items-center gap-4">
80-
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/East_Tennessee_State_Buccaneers_logo.svg/400px-East_Tennessee_State_Buccaneers_logo.svg.png" alt="ETSU Team Logo" width={40} height={40} className="w-10 h-10" />
81-
<h1 className="text-2xl font-bold">East Tennessee State <span className="text-yellow-500">Buccaneers</span> Basketball</h1>
82-
</div>
83-
<div className="flex items-center gap-4">
84-
<Button variant="outline" size="sm">
85-
Schedule
86-
</Button>
87-
<Button variant="outline" size="sm">
88-
Stats
89-
</Button>
90-
</div>
91-
</header>
92-
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
93-
{data.roster.map((r) => {
94-
return (
95-
<Card className="p-4" key={r.id}>
96-
<div className="flex items-center gap-4">
97-
<Avatar>
98-
<AvatarImage src="/placeholder.svg" alt={`${r.playerFirstName} ${r.playerLastName}`} />
99-
<AvatarFallback>{r.playerFirstName[0].toUpperCase()}{r.playerLastName[0].toUpperCase()}</AvatarFallback>
100-
</Avatar>
101-
<div>
102-
<h3 className="text-lg font-semibold">{r.playerFirstName} {r.playerLastName}</h3>
103-
<p className="text-gray-500 dark:text-gray-400">{r.playerPosition} | {heightFromInches(r.playerHeightInches)} | {r.playerWeightLbs} Lbs</p>
104-
</div>
105-
</div>
106-
</Card>
107-
)
108-
})}
109-
</div>
110-
{/*<h2 className="my-8 text-xl font-bold">Schedule</h2>
111-
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
112-
{games.map((game, index) => (
113-
<Card key={index} className="p-4">
114-
<div className="flex flex-col gap-4">
115-
<div className="flex items-center justify-between">
116-
<div className="flex items-center gap-4">
117-
<img
118-
src="/placeholder.svg"
119-
alt={`Opponent ${index + 1}`}
120-
width={40}
121-
height={40}
122-
className="w-10 h-10 rounded-full"
123-
/>
124-
<div>
125-
<h3 className="text-lg font-semibold">{game.opponent}</h3>
126-
<p className="text-gray-500 dark:text-gray-400">{game.date}</p>
127-
</div>
128-
</div>
129-
<div className="flex items-center gap-2">
130-
<div className="text-lg font-semibold">{game.score.home}</div>
131-
<div className="text-lg font-semibold">{game.score.away}</div>
132-
</div>
133-
</div>
134-
</div>
135-
</Card>
136-
))}
137-
</div>*/}
138-
</div>
139-
);
117+
<TabsContent value="roster">
118+
<h2 className="my-8 text-xl font-bold text-center sm:text-left">Roster</h2>
119+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
120+
{data.roster.map(r => {
121+
return (
122+
<Card className="p-4" key={r.id}>
123+
<div className="flex items-center gap-4">
124+
<Avatar>
125+
<AvatarImage
126+
src="/placeholder.svg"
127+
alt={`${r.playerFirstName} ${r.playerLastName}`}
128+
/>
129+
<AvatarFallback>
130+
{r.playerFirstName[0].toUpperCase()}
131+
{r.playerLastName[0].toUpperCase()}
132+
</AvatarFallback>
133+
</Avatar>
134+
<div>
135+
<h3 className="text-lg font-semibold">
136+
{r.playerFirstName} {r.playerLastName}
137+
</h3>
138+
<p className="text-gray-500 dark:text-gray-400">
139+
{r.playerPosition} |{' '}
140+
{heightFromInches(r.playerHeightInches)} |{' '}
141+
{r.playerWeightLbs} Lbs
142+
</p>
143+
</div>
144+
</div>
145+
</Card>
146+
)
147+
})}
148+
</div>
149+
</TabsContent>
150+
<TabsContent value="schedule">
151+
<h2 className="my-8 text-xl font-bold text-center sm:text-left">Schedule</h2>
152+
<p>Coming soon..</p>
153+
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
154+
{games.map((game, index) => (
155+
<Card key={index} className="p-4">
156+
<div className="flex flex-col gap-4">
157+
<div className="flex items-center justify-between">
158+
<div className="flex items-center gap-4">
159+
<img
160+
src="https://generated.vusercontent.net/placeholder.svg"
161+
alt={`Opponent ${index + 1}`}
162+
width={40}
163+
height={40}
164+
className="h-10 w-10 rounded-full"
165+
/>
166+
<div>
167+
<h3 className="text-lg font-semibold">{game.opponent}</h3>
168+
<p className="text-gray-500 dark:text-gray-400">
169+
{game.date}
170+
</p>
171+
</div>
172+
</div>
173+
<div className="flex items-center gap-2">
174+
<div className="text-lg font-semibold">
175+
{game.score.home}
176+
</div>
177+
<div className="text-lg font-semibold">
178+
{game.score.away}
179+
</div>
180+
</div>
181+
</div>
182+
</div>
183+
</Card>
184+
))}
185+
</div>
186+
</TabsContent>
187+
<TabsContent value='stats'>
188+
<h2 className="my-8 text-xl font-bold text-center sm:text-left">Stats</h2>
189+
<p>Coming soon..</p>
190+
</TabsContent>
191+
</Tabs>
192+
)
140193
}
141194

142-
export function ErrorBoundary(){
143-
const error = useRouteError();
144-
if (isRouteErrorResponse(error)) {
145-
return <div/>
146-
}
147-
return <div/>
195+
export function ErrorBoundary() {
196+
const error = useRouteError()
197+
if (isRouteErrorResponse(error)) {
198+
return <div />
199+
}
200+
return <div />
148201
}
149202

150203
export const shouldRevalidate: ShouldRevalidateFunction = () => {
151-
return true;
152-
};
204+
return true
205+
}

0 commit comments

Comments
 (0)