Compare commits
10 Commits
9410af0fb4
...
f9f8cc4ad6
| Author | SHA1 | Date | |
|---|---|---|---|
|
f9f8cc4ad6
|
|||
|
cc6e00d777
|
|||
|
03e82380db
|
|||
|
b19b5b1bd2
|
|||
|
780bb5b65a
|
|||
|
86682f6c92
|
|||
|
31c93b0876
|
|||
|
d2c59f878d
|
|||
|
1779ce0bcf
|
|||
| d219024719 |
@@ -0,0 +1,18 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
README.md
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
tags: ["v*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
type=sha
|
||||||
|
type=ref,event=tag
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
# keeps the package listing clean - attestations show up in ghcr as
|
||||||
|
# extra untagged "unknown/unknown" entries
|
||||||
|
provenance: false
|
||||||
|
# add linux/arm64 here if the host running this is arm
|
||||||
|
platforms: linux/amd64
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# --- build: needs devDependencies (vite) to produce dist/client + dist/server
|
||||||
|
FROM node:22-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm install -g pnpm@10
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
COPY . .
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# --- deps: runtime dependencies only (express, sirv, compression, react)
|
||||||
|
FROM node:22-alpine AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm install -g pnpm@10
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
|
RUN pnpm install --frozen-lockfile --prod
|
||||||
|
|
||||||
|
# --- runtime
|
||||||
|
FROM node:22-alpine AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=5173
|
||||||
|
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
COPY package.json server.js ./
|
||||||
|
|
||||||
|
EXPOSE 5173
|
||||||
|
USER node
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
|
||||||
|
CMD wget -qO- "http://127.0.0.1:${PORT}/" >/dev/null || exit 1
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
+34
-13
@@ -2,36 +2,57 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Pathway+Gothic+One&display=swap" rel="stylesheet">
|
<link
|
||||||
<link href="https://mst.fntz.net/@alex" rel="me" />
|
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- opengraph tags -->
|
<!-- opengraph tags -->
|
||||||
<meta property="og:image" content="https://storage.alexav.gg/content/picturev2.jpg" />
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://storage.alexav.gg/content/picturev2.jpg"
|
||||||
|
/>
|
||||||
<meta property="og:title" content="alex" />
|
<meta property="og:title" content="alex" />
|
||||||
<meta property="og:description" content="hi, i'm alex! i'm obsessed with the last of us and i do a lot of code things. about it tbh" />
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="hi, i'm alex! i'm obsessed with the last of us and i do a lot of code things. about it tbh"
|
||||||
|
/>
|
||||||
<meta property="og:url" content="https://alexav.gg" />
|
<meta property="og:url" content="https://alexav.gg" />
|
||||||
<meta property="og:site_name" content="alex" />
|
<meta property="og:site_name" content="alex" />
|
||||||
|
|
||||||
<!-- twitter tags -->
|
<!-- twitter tags -->
|
||||||
<meta name="twitter:title" content="alex" />
|
<meta name="twitter:title" content="alex" />
|
||||||
<meta name="twitter:description" content="hi, i'm alex! i'm obsessed with the last of us and i do a lot of code things. about it tbh" />
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="hi, i'm alex! i'm obsessed with the last of us and i do a lot of code things. about it tbh"
|
||||||
|
/>
|
||||||
<meta name="twitter:url" content="https://alexav.gg" />
|
<meta name="twitter:url" content="https://alexav.gg" />
|
||||||
<meta name="twitter:image" content="https://storage.alexav.gg/content/picturev2.jpg" />
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://storage.alexav.gg/content/picturev2.jpg"
|
||||||
|
/>
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
|
|
||||||
<!-- metadata -->
|
<!-- metadata -->
|
||||||
<meta name="title" content="alex" />
|
<meta name="title" content="alex" />
|
||||||
<meta name="description" content="hi, i'm alex! i'm obsessed with the last of us and i do a lot of code things. about it tbh" />
|
<meta
|
||||||
<meta name="keywords" content="alex frantz, frantz, alex, qa, javascript, html, react, python, java, alexavier frantz, alexavier, alexav, alexfrantz" />
|
name="description"
|
||||||
|
content="hi, i'm alex! i'm obsessed with the last of us and i do a lot of code things. about it tbh"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="alex frantz, frantz, alex, qa, javascript, html, react, python, java, alexavier frantz, alexavier, alexav, alexfrantz"
|
||||||
|
/>
|
||||||
|
|
||||||
<title>alex</title>
|
<title>alex</title>
|
||||||
|
<!--app-head-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"><!--app-html--></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/entry-client.tsx"></script>
|
||||||
<script defer data-domain="alexav.gg" src="https://metrics.alexav.gg/js/script.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
-3888
File diff suppressed because it is too large
Load Diff
+13
-5
@@ -4,25 +4,33 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "node server",
|
||||||
"dev:expose": "vite --host",
|
"build": "npm run build:client && npm run build:server",
|
||||||
"build": "tsc -b && vite build",
|
"build:client": "vite build --outDir dist/client",
|
||||||
"lint": "eslint .",
|
"build:server": "vite build --ssr src/entry-server.tsx --outDir dist/server",
|
||||||
"preview": "vite preview"
|
"preview": "cross-env NODE_ENV=production node server",
|
||||||
|
"start": "cross-env NODE_ENV=production node server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "4.1.4",
|
"@tailwindcss/vite": "4.1.4",
|
||||||
|
"compression": "^1.8.1",
|
||||||
|
"express": "^5.2.1",
|
||||||
"lucide-react": "0.488.0",
|
"lucide-react": "0.488.0",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"react-router-dom": "7.6.2",
|
"react-router-dom": "7.6.2",
|
||||||
|
"sirv": "^3.0.2",
|
||||||
"tailwindcss": "4.1.4"
|
"tailwindcss": "4.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.22.0",
|
"@eslint/js": "9.22.0",
|
||||||
|
"@types/compression": "^1.8.1",
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
|
"@types/node": "^26.1.1",
|
||||||
"@types/react": "19.0.10",
|
"@types/react": "19.0.10",
|
||||||
"@types/react-dom": "19.0.4",
|
"@types/react-dom": "19.0.4",
|
||||||
"@vitejs/plugin-react": "4.3.4",
|
"@vitejs/plugin-react": "4.3.4",
|
||||||
|
"cross-env": "^10.1.0",
|
||||||
"eslint": "9.22.0",
|
"eslint": "9.22.0",
|
||||||
"eslint-plugin-react-hooks": "5.2.0",
|
"eslint-plugin-react-hooks": "5.2.0",
|
||||||
"eslint-plugin-react-refresh": "0.4.19",
|
"eslint-plugin-react-refresh": "0.4.19",
|
||||||
|
|||||||
Generated
+3247
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: false
|
||||||
Binary file not shown.
@@ -0,0 +1,75 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import express from "express";
|
||||||
|
|
||||||
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
const port = process.env.PORT || 5173;
|
||||||
|
const base = process.env.BASE || "/";
|
||||||
|
|
||||||
|
// resolve against this file rather than the working directory, so the server
|
||||||
|
// boots the same whether it is started from the repo root or anywhere else
|
||||||
|
const root = import.meta.dirname;
|
||||||
|
const clientDir = path.resolve(root, "dist/client");
|
||||||
|
|
||||||
|
const templateHtml = isProduction
|
||||||
|
? await fs.readFile(path.resolve(clientDir, "index.html"), "utf-8")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
let vite;
|
||||||
|
if (!isProduction) {
|
||||||
|
const { createServer } = await import("vite");
|
||||||
|
vite = await createServer({
|
||||||
|
server: { middlewareMode: true },
|
||||||
|
appType: "custom",
|
||||||
|
base,
|
||||||
|
});
|
||||||
|
app.use(vite.middlewares);
|
||||||
|
} else {
|
||||||
|
const compression = (await import("compression")).default;
|
||||||
|
const sirv = (await import("sirv")).default;
|
||||||
|
app.use(compression());
|
||||||
|
app.use(base, sirv(clientDir, { extensions: [] }));
|
||||||
|
// a hashed asset that sirv did not find is genuinely gone, usually a stale
|
||||||
|
// cached document asking for a previous build. 404 it here, otherwise it
|
||||||
|
// falls through to the catch-all and answers a .js request with html
|
||||||
|
app.use(base, (req, res, next) => {
|
||||||
|
if (req.path.startsWith("/assets/")) {
|
||||||
|
res.status(404).end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use("*all", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const url = req.originalUrl.replace(base, "");
|
||||||
|
|
||||||
|
let template;
|
||||||
|
let render;
|
||||||
|
if (!isProduction) {
|
||||||
|
template = await fs.readFile(path.resolve(root, "index.html"), "utf-8");
|
||||||
|
template = await vite.transformIndexHtml(url, template);
|
||||||
|
render = (await vite.ssrLoadModule("/src/entry-server.tsx")).render;
|
||||||
|
} else {
|
||||||
|
template = templateHtml;
|
||||||
|
render = (await import("./dist/server/entry-server.js")).render;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rendered = await render(url);
|
||||||
|
const html = template
|
||||||
|
?.replace("<!--app-head-->", rendered.head ?? "")
|
||||||
|
.replace("<!--app-html-->", rendered.html ?? "");
|
||||||
|
res.status(200).set({ "Content-Type": "text/html" }).send(html);
|
||||||
|
} catch (e) {
|
||||||
|
vite?.ssrFixStacktrace(e);
|
||||||
|
console.log(e.stack);
|
||||||
|
res.status(500).end(e.stack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server started at http://localhost:${port}`);
|
||||||
|
});
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Pathway Gothic One", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 10px;
|
height: 5px;
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -18,38 +18,30 @@ export default function FantasyStatus() {
|
|||||||
)) as NFLAPIData;
|
)) as NFLAPIData;
|
||||||
|
|
||||||
// rosters
|
// rosters
|
||||||
const ownMatchup = data.matchups[1] as NFLMatchupState;
|
const ownMatchup = data.team2 as NFLMatchupState;
|
||||||
const oppMatchup = data.matchups[0] as NFLMatchupState;
|
const oppMatchup = data.team1 as NFLMatchupState;
|
||||||
|
|
||||||
setPersonalRoster({
|
setPersonalRoster(ownMatchup);
|
||||||
...ownMatchup,
|
setOpposingRoster(oppMatchup);
|
||||||
owner: data.users[1],
|
|
||||||
});
|
|
||||||
setOpposingRoster({
|
|
||||||
...oppMatchup,
|
|
||||||
owner: data.users[0],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2">
|
<div className="mt-2 space-y-4">
|
||||||
<div className="mb-4 space-y-4">
|
{personalRoster && opposingRoster && (
|
||||||
{personalRoster && opposingRoster && (
|
<>
|
||||||
<>
|
<TeamRow
|
||||||
<TeamRow
|
isWinning={personalRoster.points > opposingRoster.points}
|
||||||
isWinning={personalRoster.points > opposingRoster.points}
|
roster={personalRoster}
|
||||||
roster={personalRoster}
|
/>
|
||||||
/>
|
<TeamRow
|
||||||
<TeamRow
|
isWinning={opposingRoster.points > personalRoster.points}
|
||||||
isWinning={opposingRoster.points > personalRoster.points}
|
roster={opposingRoster}
|
||||||
roster={opposingRoster}
|
/>
|
||||||
/>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<div className="w-full flex justify-between">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={() => navigator.clipboard.writeText("alex@alexav.gg")}
|
||||||
|
className="text-xs opacity-85 hover:underline"
|
||||||
|
>
|
||||||
|
alex@alexav.gg
|
||||||
|
</a>
|
||||||
|
<p className="text-xs opacity-85">© 2026 Alex Frantz</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-1 py-4">
|
||||||
|
<h1 className="font-bold">
|
||||||
|
Alex <span className="font-normal text-blue-500">/</span> dev + QA
|
||||||
|
</h1>
|
||||||
|
<div className="flex gap-3 text-sm sm:text-base">
|
||||||
|
<a
|
||||||
|
href="https://github.com/azpha"
|
||||||
|
target="_blank"
|
||||||
|
className="hover:underline"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="hover:underline"
|
||||||
|
onClick={() => navigator.clipboard.writeText("alex@alexav.gg")}
|
||||||
|
>
|
||||||
|
alex@alexav.gg
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import Header from "./Header";
|
||||||
|
|
||||||
export default function Layout({ children }: { children: ReactNode }) {
|
export default function Layout({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-neutral-800 text-white min-h-screen flex justify-center items-center select-none">
|
<div className="bg-black text-white min-h-screen w-full max-w-[42rem] 2xl:max-w-[48rem] mx-auto select-none px-5">
|
||||||
|
<Header />
|
||||||
<div className="relative">{children}</div>
|
<div className="relative">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
interface ProjectCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
link: string;
|
||||||
|
pills: string[];
|
||||||
|
}
|
||||||
|
interface PillProps {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProjectCard({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
link,
|
||||||
|
pills,
|
||||||
|
}: ProjectCardProps) {
|
||||||
|
const Pill = ({ name }: PillProps) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-zinc-500 rounded-lg text-xs p-1 font-semibold">
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-neutral-800 rounded-lg p-2 border border-white">
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<h1 className="text-base font-bold">{title}</h1>
|
||||||
|
<a href={link} target="_blank" className="shrink-0">
|
||||||
|
<button className="bg-gray-500 border border-zinc-500 rounded-lg px-2 py-1 text-sm whitespace-nowrap">
|
||||||
|
View >
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm">{description}</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{pills.map((v, k) => {
|
||||||
|
return <Pill key={k} name={v} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,9 +14,9 @@ export default function PlayerCell({ player }: { player: NFLPlayer | null }) {
|
|||||||
|
|
||||||
if (player && player.full_name) {
|
if (player && player.full_name) {
|
||||||
return (
|
return (
|
||||||
<div className="flex-wrap flex space-x-2">
|
<div className="flex items-center gap-2">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="font-semibold text-1xl">{player.abv_name}</h1>
|
<h1 className="font-semibold text-base">{player.full_name}</h1>
|
||||||
<p>
|
<p>
|
||||||
{player?.position} · {player?.team} {injuryStatus && "·"}{" "}
|
{player?.position} · {player?.team} {injuryStatus && "·"}{" "}
|
||||||
{player?.injury_status && (
|
{player?.injury_status && (
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function TeamRow({
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 space-x-2 whitespace-nowrap overflow-x-scroll">
|
<div className="space-x-2 whitespace-nowrap overflow-x-auto pb-2">
|
||||||
{roster?.starters.map((v, k) => {
|
{roster?.starters.map((v, k) => {
|
||||||
return (
|
return (
|
||||||
<div className="inline-block" key={k}>
|
<div className="inline-block" key={k}>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { useState, useEffect } from "react";
|
|
||||||
|
|
||||||
export default function Clock() {
|
|
||||||
const [clock, setClock] = useState(
|
|
||||||
new Date().toLocaleTimeString("en-GB", {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInterval(() => {
|
|
||||||
const date = new Date().toLocaleTimeString("en-GB", {
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
if (clock != date) {
|
|
||||||
setClock(date);
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<h1 className="text-center bg-white text-black max-w-fit mx-auto px-1">
|
|
||||||
{clock}
|
|
||||||
</h1>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { useEffect, useRef } from "react";
|
|
||||||
|
|
||||||
export default function CountdownModule() {
|
|
||||||
const countdownRef = useRef<HTMLParagraphElement | null>(null);
|
|
||||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const dateOfStart = new Date("Sep 7, 2025 20:15:00").getTime();
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
const currentDate = new Date().getTime();
|
|
||||||
const difference = dateOfStart - currentDate;
|
|
||||||
|
|
||||||
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
|
|
||||||
const hours = Math.floor(
|
|
||||||
(difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
|
|
||||||
);
|
|
||||||
const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
|
|
||||||
const seconds = Math.floor((difference % (1000 * 60)) / 1000);
|
|
||||||
|
|
||||||
if (countdownRef?.current) {
|
|
||||||
let timerString = "";
|
|
||||||
timerString += `${days || "0"}d, `;
|
|
||||||
timerString += `${hours || "0"}h, `;
|
|
||||||
timerString += `${minutes || "0"}m, `;
|
|
||||||
timerString += `${seconds || "0"}s`;
|
|
||||||
|
|
||||||
if (
|
|
||||||
days <= 0 &&
|
|
||||||
hours <= 0 &&
|
|
||||||
minutes <= 0 &&
|
|
||||||
seconds <= 0 &&
|
|
||||||
timerString === ""
|
|
||||||
) {
|
|
||||||
timerString = "GO BILLS";
|
|
||||||
}
|
|
||||||
|
|
||||||
countdownRef.current.innerText = timerString;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="border border-white bg-blue-800 border-solid rounded-lg w-full mb-4">
|
|
||||||
<div className="flex items-center justify-between p-2">
|
|
||||||
<img
|
|
||||||
onClick={() => {
|
|
||||||
if (audioRef?.current) {
|
|
||||||
audioRef.current.play();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
width="50"
|
|
||||||
height="50"
|
|
||||||
className="hover:cursor-pointer"
|
|
||||||
src={"https://storage.alexav.gg/content/bills.png"}
|
|
||||||
/>
|
|
||||||
<p className="text-2xl font-bold" ref={countdownRef}></p>
|
|
||||||
</div>
|
|
||||||
<audio
|
|
||||||
ref={audioRef}
|
|
||||||
autoPlay={false}
|
|
||||||
src={
|
|
||||||
"https://storage.alexav.gg/content/0f4ce103-62d0-4ff1-8b8b-e38becced35d.mp3"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -15,19 +15,17 @@ const images = [
|
|||||||
|
|
||||||
export default function PictureCarousel() {
|
export default function PictureCarousel() {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg">
|
<div className="my-2 grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||||
<div className="my-2 space-x-2 space-y-2">
|
{images.map((v, k) => {
|
||||||
{images.map((v, k) => {
|
return (
|
||||||
return (
|
<img
|
||||||
<img
|
key={k}
|
||||||
key={k}
|
loading="lazy"
|
||||||
loading="lazy"
|
src={v}
|
||||||
src={v}
|
className="w-full h-24 object-cover rounded-md"
|
||||||
className="inline w-[200px] max-h-[100px] object-cover"
|
/>
|
||||||
/>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,68 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BillsScore, SabresScore } from "../../utils/types";
|
import { BillsScore, SabresScore, YankeesScore } from "../../utils/types";
|
||||||
|
|
||||||
|
interface TeamRowProps {
|
||||||
|
functionClick?: () => void;
|
||||||
|
width?: number;
|
||||||
|
venue: string;
|
||||||
|
date: string;
|
||||||
|
homeTeam: {
|
||||||
|
name: string;
|
||||||
|
abbreviation: string;
|
||||||
|
score: number;
|
||||||
|
logo: string;
|
||||||
|
};
|
||||||
|
awayTeam: {
|
||||||
|
name: string;
|
||||||
|
abbreviation: string;
|
||||||
|
score: number;
|
||||||
|
logo: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function TeamRow({
|
||||||
|
homeTeam,
|
||||||
|
awayTeam,
|
||||||
|
width = 50,
|
||||||
|
venue,
|
||||||
|
date,
|
||||||
|
functionClick,
|
||||||
|
}: TeamRowProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<div className="flex flex-row py-2 space-x-2">
|
||||||
|
<img width={width} className="object-cover h-20" src={homeTeam.logo} />
|
||||||
|
<img
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
(awayTeam.abbreviation === "BUF" ||
|
||||||
|
homeTeam.abbreviation === "BUF") &&
|
||||||
|
functionClick
|
||||||
|
) {
|
||||||
|
functionClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
width={width}
|
||||||
|
className="object-cover h-20"
|
||||||
|
src={awayTeam.logo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-2 text-2xl flex items-end flex-col justify-center">
|
||||||
|
<p>
|
||||||
|
{awayTeam.abbreviation} {awayTeam.score} - {homeTeam.abbreviation}{" "}
|
||||||
|
{homeTeam.score}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{venue} - {new Date(date || Date.now()).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function ScoreModule() {
|
export default function ScoreModule() {
|
||||||
const [billsScore, setBillsScore] = useState<BillsScore | null>(null);
|
const [billsScore, setBillsScore] = useState<BillsScore | null>(null);
|
||||||
const [sabresScore, setSabresScore] = useState<SabresScore | null>(null);
|
const [sabresScore, setSabresScore] = useState<SabresScore | null>(null);
|
||||||
|
const [yankeesScore, setYankeesScore] = useState<YankeesScore | null>(null);
|
||||||
const [warpath, setWarpath] = useState<boolean>(false);
|
const [warpath, setWarpath] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -22,113 +81,64 @@ export default function ScoreModule() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setSabresScore(data);
|
setSabresScore(data);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to fetch Sabresscore; " + res.statusText);
|
console.error("Failed to fetch Sabres score; " + res.statusText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function fetchYankeesScore() {
|
||||||
|
const res = await fetch("https://api.alexav.gg/v4/sports/scores/yankees");
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
setYankeesScore(data);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch Yankees score; " + res.statusText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchYankeesScore();
|
||||||
fetchBillsScore();
|
fetchBillsScore();
|
||||||
fetchSabresScore();
|
fetchSabresScore();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg w-full mb-2">
|
<div className="rounded-lg w-full">
|
||||||
<div className="flex flex-row justify-between">
|
{sabresScore?.homeTeam && sabresScore.awayTeam && (
|
||||||
<div className="flex flex-row py-2">
|
<TeamRow
|
||||||
<img
|
homeTeam={sabresScore.homeTeam}
|
||||||
onClick={() => {
|
awayTeam={sabresScore.awayTeam}
|
||||||
if (sabresScore?.homeTeam.abbreviation === "BUF")
|
date={sabresScore.date}
|
||||||
setWarpath(true);
|
venue={sabresScore.venue}
|
||||||
}}
|
functionClick={() => setWarpath(true)}
|
||||||
width="80"
|
/>
|
||||||
className="shrink-0"
|
)}
|
||||||
src={sabresScore?.homeTeam.logo}
|
{billsScore?.homeTeam && billsScore.awayTeam && (
|
||||||
/>
|
<TeamRow
|
||||||
<img
|
homeTeam={billsScore.homeTeam}
|
||||||
onClick={() => {
|
awayTeam={billsScore.awayTeam}
|
||||||
if (sabresScore?.awayTeam.abbreviation === "BUF")
|
width={50}
|
||||||
setWarpath(true);
|
date={billsScore.gameDay}
|
||||||
}}
|
venue={billsScore.venue}
|
||||||
width="80"
|
/>
|
||||||
className="shrink-0"
|
)}
|
||||||
src={sabresScore?.awayTeam.logo}
|
{yankeesScore?.homeTeam && yankeesScore.awayTeam && (
|
||||||
/>
|
<TeamRow
|
||||||
</div>
|
homeTeam={yankeesScore.homeTeam}
|
||||||
<div className="p-2 text-2xl flex items-end flex-col">
|
awayTeam={yankeesScore.awayTeam}
|
||||||
<p>
|
venue={yankeesScore.venue}
|
||||||
{sabresScore?.homeTeam.abbreviation} {sabresScore?.homeTeam.score} -{" "}
|
date={yankeesScore.date}
|
||||||
{sabresScore?.awayTeam.abbreviation} {sabresScore?.awayTeam.score}
|
/>
|
||||||
</p>
|
)}
|
||||||
<p className="text-sm">
|
|
||||||
{sabresScore?.venue} -{" "}
|
|
||||||
{new Date(sabresScore?.date || Date.now()).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{warpath && (
|
{warpath && (
|
||||||
<audio
|
<audio
|
||||||
src={"https://storage.alexav.gg/content/warpath.mp3"}
|
src={"https://storage.alexav.gg/content/warpath.mp3"}
|
||||||
autoPlay={true}
|
autoPlay={true}
|
||||||
onTimeUpdate={(e) => {
|
onTimeUpdate={(e) => {
|
||||||
if (e.currentTarget.duration === e.currentTarget.currentTime) {
|
if (e.currentTarget.duration === e.currentTarget.currentTime) {
|
||||||
setWarpath(false);
|
setWarpath(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className="flex flex-row justify-between">
|
|
||||||
<div className="flex flex-row space-x-6 px-4 items-center">
|
|
||||||
<img
|
|
||||||
width="80"
|
|
||||||
className="w-full h-[50px]"
|
|
||||||
src={billsScore?.homeTeam.logo}
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
width="80"
|
|
||||||
className="w-full h-[50px]"
|
|
||||||
src={billsScore?.awayTeam.logo}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="p-2 text-2xl flex items-end flex-col">
|
|
||||||
<p>
|
|
||||||
{billsScore?.homeTeam.abbreviation} {billsScore?.homeTeam.score} -{" "}
|
|
||||||
{billsScore?.awayTeam.abbreviation} {billsScore?.awayTeam.score}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
{billsScore?.venue} -{" "}
|
|
||||||
{new Date(billsScore?.gameDay || Date.now()).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <div className="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
onClick={() => {
|
|
||||||
if (type === "sabres") {
|
|
||||||
setWarpath(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
width="100"
|
|
||||||
className="p-4"
|
|
||||||
src={IMAGES[type]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-center items-center px-6">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl">
|
|
||||||
{type === "bills"
|
|
||||||
? billsScore?.name
|
|
||||||
: `${sabresScore?.awayTeam.abbrev || "BUF"} @ ${sabresScore?.homeTeam.abbrev || "BUF"}`}
|
|
||||||
</h1>
|
|
||||||
<p className="float-right text-[20px]">
|
|
||||||
{type === "bills"
|
|
||||||
? billsScore?.score
|
|
||||||
: `${sabresScore && sabresScore?.sabresScore > sabresScore?.oppScore ? "W" : "L"} ${sabresScore?.sabresScore || 0}-${sabresScore?.oppScore || 0}`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { hydrateRoot } from "react-dom/client";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import "./assets/main.css";
|
||||||
|
import App from "./pages/App.tsx";
|
||||||
|
|
||||||
|
hydrateRoot(
|
||||||
|
document.getElementById("root")!,
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { renderToString } from "react-dom/server";
|
||||||
|
import * as RouterExports from "react-router-dom";
|
||||||
|
import App from "./pages/App";
|
||||||
|
|
||||||
|
// Vite's SSR runner resolves react-router-dom to its CommonJS build, so the
|
||||||
|
// named exports land on `.default`; fall back to the namespace when not.
|
||||||
|
const { StaticRouter } =
|
||||||
|
(RouterExports as { default?: typeof RouterExports }).default ?? RouterExports;
|
||||||
|
|
||||||
|
export function render(url: string) {
|
||||||
|
const html = renderToString(
|
||||||
|
<StaticRouter location={url}>
|
||||||
|
<App />
|
||||||
|
</StaticRouter>,
|
||||||
|
);
|
||||||
|
return { html };
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { createRoot } from "react-dom/client";
|
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
|
||||||
import "./assets/main.css";
|
|
||||||
import App from "./pages/App.tsx";
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
element: <App />,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
|
||||||
<RouterProvider router={router} />
|
|
||||||
);
|
|
||||||
+90
-49
@@ -1,63 +1,104 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import Layout from "../components/Layout";
|
import Layout from "../components/Layout";
|
||||||
import Clock from "../components/modules/Clock";
|
import ProjectCard from "../components/ProjectCard";
|
||||||
import Button from "../components/Button";
|
|
||||||
import Home from "./Home";
|
|
||||||
import ScoreModule from "../components/modules/ScoreModule";
|
|
||||||
import PicturesPage from "./Pictures";
|
|
||||||
import FantasyStatus from "../components/FantasyStatus";
|
import FantasyStatus from "../components/FantasyStatus";
|
||||||
|
import PictureCarousel from "../components/modules/PictureCarousel";
|
||||||
|
import Footer from "../components/Footer";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [tab, setTab] = useState<"home" | "scores" | "fantasy" | "pictures">(
|
|
||||||
"home",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="border-3 bg-gradient-to-t from-[#003087] to-[#FFB81C] border-black w-full sm:w-[500px]">
|
<main className="my-12 sm:my-20">
|
||||||
<div className="bg-[#003087] w-full">
|
<div className="py-2">
|
||||||
<div className="grid grid-cols-3 gap-2 p-2 text-2xl">
|
<p className="opacity-85 text-sm text-left">HI, I'M ALEX</p>
|
||||||
<h1 className="font-semibold text-white">welcome</h1>
|
<h1 className="text-3xl sm:text-4xl text-balance py-2">
|
||||||
<Clock />
|
I write software sometimes, then I spend twice as long breaking it
|
||||||
<h1 className="text-right text-transparent font-bold bg-clip-text bg-gradient-to-t from-[#003087] to-[#FFB81C]">
|
</h1>
|
||||||
GO SABRES!
|
</div>
|
||||||
|
<div className="py-2 space-y-6">
|
||||||
|
<p>
|
||||||
|
My day job is software QA at{" "}
|
||||||
|
<a
|
||||||
|
className="text-lime-300 hover:underline"
|
||||||
|
href="https://medal.tv/u/alexav"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Medal.tv
|
||||||
|
</a>{" "}
|
||||||
|
(
|
||||||
|
<a
|
||||||
|
href="https://medal.tv/careers"
|
||||||
|
target="_blank"
|
||||||
|
className="underline"
|
||||||
|
>
|
||||||
|
we're hiring!
|
||||||
|
</a>
|
||||||
|
), then by night I play with my homelab rack or write some mediocre
|
||||||
|
JavaScript.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Based in Western New York (go bills, go sabres). Working on learning
|
||||||
|
Java, C#. Pointless software for funzies is my passion.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-4 border-zinc-800" />
|
||||||
|
|
||||||
|
<p className="opacity-85 text-sm text-left pb-2">THINGS I'VE BUILT</p>
|
||||||
|
<div className="my-2 space-y-2">
|
||||||
|
<ProjectCard
|
||||||
|
title={"LED Matrix Scoreboard"}
|
||||||
|
description={
|
||||||
|
"A physical scoreboard driven by an LED matrix, pulls live sports data and renders pixel by pixel."
|
||||||
|
}
|
||||||
|
link="https://git.alexav.gg/alex/scoreboard"
|
||||||
|
pills={["Python", "Raspberry Pi", "Hardware"]}
|
||||||
|
/>
|
||||||
|
<ProjectCard
|
||||||
|
title={"Subber"}
|
||||||
|
description={
|
||||||
|
"Keeps track on your subscriptions and generates clear metrics for you to manage."
|
||||||
|
}
|
||||||
|
link="https://github.com/azpha/subber"
|
||||||
|
pills={["TypeScript", "Tailwind", "Prisma"]}
|
||||||
|
/>
|
||||||
|
<ProjectCard
|
||||||
|
title={"Snapchat Memory Extractor"}
|
||||||
|
description={
|
||||||
|
"Extracts your Snapchat memories from your data export in a cleaner format than their horrible index.html."
|
||||||
|
}
|
||||||
|
link="https://github.com/azpha/snapchat-memory-extractor"
|
||||||
|
pills={["Python", "Automation"]}
|
||||||
|
/>
|
||||||
|
<ProjectCard
|
||||||
|
title={"Homelab"}
|
||||||
|
description={
|
||||||
|
"My tinkering sandbox. Full CI/CD pipeline for people to learn & take inspiration from."
|
||||||
|
}
|
||||||
|
link="https://git.alexav.gg/alex/homelab"
|
||||||
|
pills={["Docker", "Ansible", "Python", "Automation"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="my-4 border-zinc-800" />
|
||||||
|
|
||||||
|
<p className="opacity-85 text-sm text-left pb-2">FUN STUFF</p>
|
||||||
|
<div className="my-2 space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-sm opacity-85 font-semibold">Fantasy</h1>
|
||||||
|
<FantasyStatus />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-sm opacity-85 font-semibold">
|
||||||
|
My Life, Pictured
|
||||||
</h1>
|
</h1>
|
||||||
|
<PictureCarousel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img
|
<hr className="my-4 border-zinc-800" />
|
||||||
className="h-[100px] object-[25%_35%] w-full object-cover border-b-black border-b-2 border-t-2 border-t-black"
|
|
||||||
style={{
|
|
||||||
objectPosition: "",
|
|
||||||
}}
|
|
||||||
src="https://storage.alexav.gg/content/sabres-board.jpg"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex flex-row space-x-2 justify-center text-2xl my-2">
|
<Footer />
|
||||||
<Button active={tab === "home"} onClick={() => setTab("home")}>
|
</main>
|
||||||
home
|
|
||||||
</Button>
|
|
||||||
<Button active={tab === "scores"} onClick={() => setTab("scores")}>
|
|
||||||
scores
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
active={tab === "pictures"}
|
|
||||||
onClick={() => setTab("pictures")}
|
|
||||||
>
|
|
||||||
pictures
|
|
||||||
</Button>
|
|
||||||
<Button active={tab === "fantasy"} onClick={() => setTab("fantasy")}>
|
|
||||||
fantasy
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr className="border-black border-1" />
|
|
||||||
|
|
||||||
{tab === "home" && <Home />}
|
|
||||||
{tab === "scores" && <ScoreModule />}
|
|
||||||
{tab === "pictures" && <PicturesPage />}
|
|
||||||
{tab === "fantasy" && <FantasyStatus />}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="bg-black text-white px-1">teams</span> bills,
|
<span className="bg-black text-white px-1">teams</span> bills,
|
||||||
sabres, jays
|
sabres, yanks
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import PictureCarousel from "../components/modules/PictureCarousel";
|
|||||||
export default function PicturesPage() {
|
export default function PicturesPage() {
|
||||||
return (
|
return (
|
||||||
<div className="p-2 flex flex-col justify-center">
|
<div className="p-2 flex flex-col justify-center">
|
||||||
<h1 className="text-2xl font-bold">pictures</h1>
|
<div className="h-96 w-full overflow-y-auto">
|
||||||
|
|
||||||
<div className="h-96 overflow-y-auto">
|
|
||||||
<PictureCarousel />
|
<PictureCarousel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import FantasyStatus from "../components/FantasyStatus";
|
||||||
|
import ScoreModule from "../components/modules/ScoreModule";
|
||||||
|
|
||||||
|
export default function Sports() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="p-2">
|
||||||
|
<ScoreModule />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr className="border-black border-1" />
|
||||||
|
|
||||||
|
<div className="p-2">
|
||||||
|
<h1 className="text-2xl font-semibold">fantasy</h1>
|
||||||
|
<FantasyStatus />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+19
-2
@@ -12,13 +12,15 @@ type LastFMImageObject = {
|
|||||||
// nfl stuff
|
// nfl stuff
|
||||||
interface NFLAPIData {
|
interface NFLAPIData {
|
||||||
nflState: NFLState;
|
nflState: NFLState;
|
||||||
matchups: NFLMatchupState[];
|
team1: NFLMatchupState;
|
||||||
|
team2: NFLMatchupState;
|
||||||
users: SleeperUser[];
|
users: SleeperUser[];
|
||||||
}
|
}
|
||||||
interface NFLMatchupState {
|
interface NFLMatchupState {
|
||||||
points: number;
|
points: number;
|
||||||
owner: SleeperUser;
|
owner: SleeperUser;
|
||||||
starters: NFLPlayer[];
|
starters: NFLPlayer[];
|
||||||
|
players: NFLPlayer[];
|
||||||
starters_points: number[];
|
starters_points: number[];
|
||||||
players_points: object;
|
players_points: object;
|
||||||
}
|
}
|
||||||
@@ -63,9 +65,17 @@ type SabresScore = {
|
|||||||
awayTeam: NHLTeam;
|
awayTeam: NHLTeam;
|
||||||
homeTeam: NHLTeam;
|
homeTeam: NHLTeam;
|
||||||
};
|
};
|
||||||
|
type YankeesScore = {
|
||||||
|
season: number;
|
||||||
|
week: number;
|
||||||
|
date: string;
|
||||||
|
venue: string;
|
||||||
|
awayTeam: MLBTeam;
|
||||||
|
homeTeam: MLBTeam;
|
||||||
|
};
|
||||||
type NFLTeam = {
|
type NFLTeam = {
|
||||||
abbreviation: string;
|
abbreviation: string;
|
||||||
score: string;
|
score: number;
|
||||||
name: string;
|
name: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
@@ -76,6 +86,12 @@ type NHLTeam = {
|
|||||||
score: number;
|
score: number;
|
||||||
logo: string;
|
logo: string;
|
||||||
};
|
};
|
||||||
|
type MLBTeam = {
|
||||||
|
name: string;
|
||||||
|
abbreviation: string;
|
||||||
|
logo: string;
|
||||||
|
score: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
LastFMMusicObject,
|
LastFMMusicObject,
|
||||||
@@ -87,4 +103,5 @@ export type {
|
|||||||
NFLPlayer,
|
NFLPlayer,
|
||||||
BillsScore,
|
BillsScore,
|
||||||
SabresScore,
|
SabresScore,
|
||||||
|
YankeesScore,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"types": ["node"],
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user