Skip to content

Commit b41504b

Browse files
committed
feat: bucnation page
1 parent 97a76e6 commit b41504b

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

app/components/ui/avatar.tsx

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as React from "react"
2+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
3+
4+
import { cn } from "#app/utils/misc.tsx"
5+
6+
const Avatar = React.forwardRef<
7+
React.ElementRef<typeof AvatarPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
9+
>(({ className, ...props }, ref) => (
10+
<AvatarPrimitive.Root
11+
ref={ref}
12+
className={cn(
13+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14+
className
15+
)}
16+
{...props}
17+
/>
18+
))
19+
Avatar.displayName = AvatarPrimitive.Root.displayName
20+
21+
const AvatarImage = React.forwardRef<
22+
React.ElementRef<typeof AvatarPrimitive.Image>,
23+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24+
>(({ className, ...props }, ref) => (
25+
<AvatarPrimitive.Image
26+
ref={ref}
27+
className={cn("aspect-square h-full w-full", className)}
28+
{...props}
29+
/>
30+
))
31+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
32+
33+
const AvatarFallback = React.forwardRef<
34+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
35+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36+
>(({ className, ...props }, ref) => (
37+
<AvatarPrimitive.Fallback
38+
ref={ref}
39+
className={cn(
40+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
41+
className
42+
)}
43+
{...props}
44+
/>
45+
))
46+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47+
48+
export { Avatar, AvatarImage, AvatarFallback }

app/routes/bucnation.tsx

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import type { HeadersFunction, LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node";
2+
import { json } from '@remix-run/node';
3+
import { MetaFunction, useLoaderData, isRouteErrorResponse, useRouteError } from "@remix-run/react";
4+
import type { ShouldRevalidateFunction } from "@remix-run/react";
5+
import { Button } from "#app/components/ui/button";
6+
import { Card } from "#app/components/ui/card";
7+
import { Avatar, AvatarFallback, AvatarImage } from "#app/components/ui/avatar"
8+
9+
10+
interface VCActivityType {
11+
id: number
12+
activeFlag: string
13+
status: string
14+
playerFirstName: string
15+
playerLastName: string
16+
playerPosition: string
17+
playerWeightLbs: number
18+
playerHeightInches: number
19+
playerEligibilityYear: string
20+
}
21+
22+
export const meta: MetaFunction = ({ data }) => [
23+
24+
{ title: data ? 'lucasb.dev | BucNation' : 'Error | lucasb.dev' },
25+
{
26+
name: 'description',
27+
content: `A page dedicated to East Tennessee State Basketball`,
28+
},
29+
30+
];
31+
32+
export const headers: HeadersFunction = () => (
33+
{
34+
// your headers here
35+
}
36+
);
37+
38+
export const loader = async ({ request }: LoaderFunctionArgs) => {
39+
const reqHeaders = {
40+
Pb: "MV7mOE51zp9clOm7",
41+
"User-Agent":
42+
"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",
43+
};
44+
45+
const response = await fetch("https://api.verbalcommits.com/vc/schools/chart/etsu", {
46+
headers: reqHeaders,
47+
});
48+
const data = await response.json() as VCActivityType[];
49+
const filteredResults = data.filter((d) => d.status === 'ENROLLMENT');
50+
51+
return json({ roster: filteredResults });
52+
};
53+
54+
export const action = async ({ request }: ActionFunctionArgs) => {
55+
return null;
56+
};
57+
58+
const heightFromInches = (total: number) => {
59+
const feet = Math.floor(total / 12);
60+
const inches = total % 12;
61+
return `${feet}'${inches}"`;
62+
}
63+
64+
export default function BucNation(){
65+
const data = useLoaderData<typeof loader>();
66+
67+
/* const games = [
68+
{
69+
opponent: "Los Angeles Lakers",
70+
date: "October 18, 2023",
71+
score: {
72+
home: 112,
73+
away: 104,
74+
},
75+
image: "/opponent1.jpg",
76+
}]; */
77+
78+
return (
79+
<div className="w-full max-w-6xl mx-auto py-8 px-4 md:px-6">
80+
<header className="flex items-center justify-between mb-8">
81+
<div className="flex items-center gap-4">
82+
<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" />
83+
<h1 className="text-2xl font-bold">East Tennessee State <span className="text-yellow-500">Buccaneers</span> Basketball</h1>
84+
</div>
85+
<div className="flex items-center gap-4">
86+
<Button variant="outline" size="sm">
87+
Schedule
88+
</Button>
89+
<Button variant="outline" size="sm">
90+
Stats
91+
</Button>
92+
</div>
93+
</header>
94+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
95+
{data.roster.map((r) => {
96+
return (
97+
<Card className="p-4" key={r.id}>
98+
<div className="flex items-center gap-4">
99+
<Avatar>
100+
<AvatarImage src="/placeholder.svg" alt={`${r.playerFirstName} ${r.playerLastName}`} />
101+
<AvatarFallback>{r.playerFirstName[0].toUpperCase()}{r.playerLastName[0].toUpperCase()}</AvatarFallback>
102+
</Avatar>
103+
<div>
104+
<h3 className="text-lg font-semibold">{r.playerFirstName} {r.playerLastName}</h3>
105+
<p className="text-gray-500 dark:text-gray-400">{r.playerPosition} | {heightFromInches(r.playerHeightInches)} | {r.playerWeightLbs} Lbs</p>
106+
</div>
107+
</div>
108+
</Card>
109+
)
110+
})}
111+
</div>
112+
{/*<h2 className="my-8 text-xl font-bold">Schedule</h2>
113+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
114+
{games.map((game, index) => (
115+
<Card key={index} className="p-4">
116+
<div className="flex flex-col gap-4">
117+
<div className="flex items-center justify-between">
118+
<div className="flex items-center gap-4">
119+
<img
120+
src="/placeholder.svg"
121+
alt={`Opponent ${index + 1}`}
122+
width={40}
123+
height={40}
124+
className="w-10 h-10 rounded-full"
125+
/>
126+
<div>
127+
<h3 className="text-lg font-semibold">{game.opponent}</h3>
128+
<p className="text-gray-500 dark:text-gray-400">{game.date}</p>
129+
</div>
130+
</div>
131+
<div className="flex items-center gap-2">
132+
<div className="text-lg font-semibold">{game.score.home}</div>
133+
<div className="text-lg font-semibold">{game.score.away}</div>
134+
</div>
135+
</div>
136+
</div>
137+
</Card>
138+
))}
139+
</div>*/}
140+
</div>
141+
);
142+
}
143+
144+
export function ErrorBoundary(){
145+
const error = useRouteError();
146+
if (isRouteErrorResponse(error)) {
147+
return <div/>
148+
}
149+
return <div/>
150+
}
151+
152+
export const shouldRevalidate: ShouldRevalidateFunction = () => {
153+
return true;
154+
};

package-lock.json

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@nasa-gcn/remix-seo": "2.0.0",
4848
"@paralleldrive/cuid2": "2.2.2",
4949
"@prisma/client": "5.13.0",
50+
"@radix-ui/react-avatar": "1.0.4",
5051
"@radix-ui/react-checkbox": "1.0.4",
5152
"@radix-ui/react-dialog": "1.0.5",
5253
"@radix-ui/react-dropdown-menu": "2.0.6",

0 commit comments

Comments
 (0)