Remove Netlify, make an API server

This commit is contained in:
Taevas 2025-03-06 22:18:15 +01:00
parent 0ea59d4bd0
commit ec6cb4b355
73 changed files with 2238 additions and 334 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
.netlify .netlify
node_modules node_modules
dist dist
*.log .env

View file

@ -1,5 +1,3 @@
[![Netlify Status](https://api.netlify.com/api/v1/badges/10889a9b-c148-488d-aecd-9a44e0cf6f46/deploy-status)](https://taevas.xyz)
# taevas.xyz # taevas.xyz
My personal website! My personal website!
@ -7,18 +5,16 @@ My personal website!
## Build and develop ## Build and develop
```bash ```bash
bun install --global netlify-cli bun i
bun i --ignore-scripts bun dev
netlify dev
``` ```
## Environment variables ## Environment variables
This package uses [`@carbon/icons-react`](https://github.com/carbon-design-system/carbon/tree/main/packages/icons-react), which **installs [a telemetry package which can be disabled](https://github.com/ibm-telemetry/telemetry-js/tree/main#opting-out-of-ibm-telemetry-data-collection):** This package uses [`@carbon/icons-react`](https://github.com/carbon-design-system/carbon/tree/main/packages/icons-react), which **installs [a telemetry package which can be disabled](https://github.com/ibm-telemetry/telemetry-js/tree/main#opting-out-of-ibm-telemetry-data-collection):**
```bash Set the environment variable IBM_TELEMETRY_DISABLED to true
netlify env:set IBM_TELEMETRY_DISABLED true
```
This package makes use of several online APIs through Netlify in order to deliver the `Infos` that are available on the right side of the website, accessing most of these APIs requires a key (or similar), which can be set through the following environment variables: This package makes use of several online APIs through Netlify in order to deliver the `Infos` that are available on the right side of the website, accessing most of these APIs requires a key (or similar), which can be set through the following environment variables:

View file

@ -1,9 +1,9 @@
import {type Handler} from "@netlify/functions";
import {Octokit} from "@octokit/rest"; import {Octokit} from "@octokit/rest";
import {type GithubInfo} from "#Infos/Coding/GitHub.js"; import {type GithubInfo} from "#Infos/Coding/GitHub.tsx";
import type { Handler } from "..";
const handler: Handler = async () => { export const coding_github: Handler = async () => {
const octokit = new Octokit({auth: process.env.API_GITHUB}); const octokit = new Octokit({auth: process.env["API_GITHUB"]});
const github = await octokit.rest.activity.listEventsForAuthenticatedUser({username: "TTTaevas"}); const github = await octokit.rest.activity.listEventsForAuthenticatedUser({username: "TTTaevas"});
const publicPush = github.data.find((e) => (e.type === "PushEvent" || e.type === "PullRequestEvent") && e.public); const publicPush = github.data.find((e) => (e.type === "PushEvent" || e.type === "PullRequestEvent") && e.public);
@ -19,10 +19,7 @@ const handler: Handler = async () => {
} : undefined, } : undefined,
}; };
return { return new Response(new Blob([JSON.stringify(info)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(info), }), {status: 200});
};
}; };
export {handler};

21
api/coding_gitlab.ts Normal file
View file

@ -0,0 +1,21 @@
import { Gitlab } from "@gitbeaker/rest";
import {type GitlabInfo} from "#Infos/Coding/GitLab.tsx";
import type { Handler } from "..";
export const coding_gitlab: Handler = async () => {
const api = new Gitlab({token: process.env["API_GITLAB"]!});
const gitlab = await api.Events.all({action: "pushed"});
const created_at = gitlab.at(0)?.created_at;
if (typeof created_at !== "string") {
return new Response("Not Found", {status: 404});
}
const activity: GitlabInfo = {
date: created_at.substring(0, created_at.indexOf("T")),
};
return new Response(new Blob([JSON.stringify(activity)], {
type: "application/json",
}), {status: 200});
};

View file

@ -1,7 +1,7 @@
import {type Handler} from "@netlify/functions"; import { type KitsudevInfo } from "#Infos/Coding/KitsuDev.tsx";
import { type KitsudevInfo } from "#Infos/Coding/KitsuDev.js"; import type { Handler } from "..";
const handler: Handler = async () => { export const coding_kitsudev: Handler = async () => {
const kitsudev = await (await fetch("https://kitsunes.dev/api/v1/users/Taevas/activities/feeds?limit=1")).json() as [{ const kitsudev = await (await fetch("https://kitsunes.dev/api/v1/users/Taevas/activities/feeds?limit=1")).json() as [{
repo: { repo: {
full_name: string full_name: string
@ -16,10 +16,7 @@ const handler: Handler = async () => {
date: kitsudev[0].created date: kitsudev[0].created
}; };
return { return new Response(new Blob([JSON.stringify(info)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(info), }), {status: 200});
};
}; };
export {handler};

View file

@ -1,11 +1,11 @@
import {type Handler} from "@netlify/functions"; import { type KitsuclubInfo } from "#Infos/Fediverse/KitsuClub.tsx";
import { KitsuclubInfo } from "#Infos/Fediverse/KitsuClub.js"; import type { Handler } from "..";
const handler: Handler = async () => { export const fediverse_kitsuclub: Handler = async () => {
const kitsuclub = await (await fetch("https://kitsunes.club/api/users/notes", { const kitsuclub = await (await fetch("https://kitsunes.club/api/users/notes", {
method: "POST", method: "POST",
headers: { headers: {
"Authorization": `Bearer ${process.env.API_KITSUCLUB}`, "Authorization": `Bearer ${process.env["API_KITSUCLUB"]}`,
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
@ -42,9 +42,7 @@ const handler: Handler = async () => {
const details = kitsuclub.at(Math.max(0, kitsuclub.length - 1)); const details = kitsuclub.at(Math.max(0, kitsuclub.length - 1));
if (!details) { if (!details) {
return { return new Response("Not Found", {status: 404});
statusCode: 404,
};
} }
let scan_text = details.text; let scan_text = details.text;
@ -89,10 +87,7 @@ const handler: Handler = async () => {
}) })
}; };
return { return new Response(new Blob([JSON.stringify(activity)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(activity), }), {status: 200});
};
}; };
export {handler};

32
api/gaming_osu.ts Normal file
View file

@ -0,0 +1,32 @@
import * as osu from "osu-api-v2-js";
import {type OsuInfo} from "#Infos/Gaming/Osu.tsx";
import {MongoClient} from "mongodb";
import {type Token} from "./token.tsx";
import type { Handler } from "../index.ts";
export const gaming_osu: Handler = async (params) => {
const client = new MongoClient(process.env["URL_MONGODB"]!);
await client.connect();
const db = client.db("tokens");
const collection = db.collection<Token>("osu");
const token = await collection.findOne();
void client.close();
let ruleset = params.has("ruleset") ? Number(params.get("ruleset")) : undefined;
if (ruleset && isNaN(ruleset)) {ruleset = undefined;}
const api = new osu.API({access_token: token?.access_token});
const profile = await api.getUser(7276846, ruleset);
const info: OsuInfo = {
country: profile.country.name,
ranks: {
global: profile.statistics.global_rank ?? 0,
country: profile.statistics.country_rank ?? 0,
},
};
return new Response(new Blob([JSON.stringify(info)], {
type: "application/json",
}), {status: 200});
};

View file

@ -1,5 +1,5 @@
import {type Handler} from "@netlify/functions"; import {type SpeedruncomInfo} from "#Infos/Gaming/Speedruncom.tsx";
import {type SpeedruncomInfo} from "#Infos/Gaming/Speedruncom.js"; import type { Handler } from "..";
interface Runs { interface Runs {
data: { data: {
@ -41,15 +41,13 @@ interface Level {
}; };
} }
const handler: Handler = async () => { export const gaming_speedruncom: Handler = async () => {
// using the API's embedding would be stupid here, as that'd create lag due to irrelevant runs // using the API's embedding would be stupid here, as that'd create lag due to irrelevant runs
const speedruncom = await (await fetch("https://www.speedrun.com/api/v1/users/j03v45mj/personal-bests")).json() as Runs; const speedruncom = await (await fetch("https://www.speedrun.com/api/v1/users/j03v45mj/personal-bests")).json() as Runs;
const data = speedruncom.data.at(0); const data = speedruncom.data.at(0);
if (!data) { if (!data) {
return { return new Response("Not Found", {status: 404});
statusCode: 404,
};
} }
const urlsToRequest = [`https://www.speedrun.com/api/v1/games/${data.run.game}`]; const urlsToRequest = [`https://www.speedrun.com/api/v1/games/${data.run.game}`];
@ -76,10 +74,9 @@ const handler: Handler = async () => {
run.time = run.time.substring(1); run.time = run.time.substring(1);
} }
return { return new Response(new Blob([JSON.stringify(run)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(run), }), {status: 200});
};
}; };
// https://gist.github.com/vankasteelj/74ab7793133f4b257ea3 // https://gist.github.com/vankasteelj/74ab7793133f4b257ea3
@ -92,5 +89,3 @@ function sec2time(timeInSeconds: number) {
const milliseconds = Number(time.toString().slice(-3)); const milliseconds = Number(time.toString().slice(-3));
return pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3); return pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3);
}; };
export {handler};

View file

@ -1,7 +1,7 @@
import {type Handler} from "@netlify/functions"; import {type HacktheboxInfo} from "#Infos/Hacking/Hackthebox.tsx";
import {type HacktheboxInfo} from "#Infos/Hacking/Hackthebox.js"; import type { Handler } from "..";
const handler: Handler = async () => { export const hacking_hackthebox: Handler = async () => {
const hackthebox = await (await fetch("https://www.hackthebox.com/api/v4/profile/activity/1063999")).json() as { const hackthebox = await (await fetch("https://www.hackthebox.com/api/v4/profile/activity/1063999")).json() as {
profile: { profile: {
activity: HacktheboxInfo[]; activity: HacktheboxInfo[];
@ -10,19 +10,13 @@ const handler: Handler = async () => {
const pwn = hackthebox.profile.activity.find((a: HacktheboxInfo) => a?.object_type === "machine"); const pwn = hackthebox.profile.activity.find((a: HacktheboxInfo) => a?.object_type === "machine");
if (!pwn) { if (!pwn) {
return { return new Response("Not Found", {status: 404});
statusCode: 404,
body: "",
};
} }
pwn.machine_avatar = `https://www.hackthebox.com${pwn.machine_avatar}`; pwn.machine_avatar = `https://www.hackthebox.com${pwn.machine_avatar}`;
pwn.date = pwn.date.substring(0, pwn.date.indexOf("T")); pwn.date = pwn.date.substring(0, pwn.date.indexOf("T"));
return { return new Response(new Blob([JSON.stringify(pwn)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(pwn), }), {status: 200});
};
}; };
export {handler};

View file

@ -1,6 +1,6 @@
import {type Handler} from "@netlify/functions"; import {type WanikaniInfo} from "#Infos/Japanese/Wanikani.tsx";
import {type WanikaniInfo} from "#Infos/Japanese/Wanikani.js"; import type { WKLevelProgression, WKResetCollection, WKSummary } from "@bachmacintosh/wanikani-api-types";
import { WKLevelProgression, WKResetCollection, WKSummary } from "@bachmacintosh/wanikani-api-types"; import type { Handler } from "..";
interface Subject { interface Subject {
id: number; id: number;
@ -48,7 +48,7 @@ function addStuffToLearn(ids: number[], data: {available_at: string; subject_ids
return arr; return arr;
} }
const handler: Handler = async () => { export const japanese_wanikani: Handler = async () => {
const urlsToRequest = [ const urlsToRequest = [
"https://api.wanikani.com/v2/level_progressions", "https://api.wanikani.com/v2/level_progressions",
"https://api.wanikani.com/v2/resets", "https://api.wanikani.com/v2/resets",
@ -56,8 +56,8 @@ const handler: Handler = async () => {
]; ];
const toRequest = urlsToRequest.map((url) => new Promise(async (resolve) => { const toRequest = urlsToRequest.map((url) => new Promise(async (resolve) => {
const response = await fetch(url, {headers: { const response = await fetch(url, {headers: {
"Authorization": `Bearer ${process.env.API_WANIKANI}`, "Authorization": `Bearer ${process.env["API_WANIKANI"]}`,
"Content-Type": "application/json", "Content-Type": "application.json",
}}); }});
resolve(await response.json()); resolve(await response.json());
})); }));
@ -92,8 +92,8 @@ const handler: Handler = async () => {
const subjectIdsAll = subjectIdsLessons.concat(subjectIdsReviews); const subjectIdsAll = subjectIdsLessons.concat(subjectIdsReviews);
const subjects = await (await fetch(`https://api.wanikani.com/v2/subjects?ids=${subjectIdsAll.toString()}`, {headers: { const subjects = await (await fetch(`https://api.wanikani.com/v2/subjects?ids=${subjectIdsAll.toString()}`, {headers: {
"Authorization": `Bearer ${process.env.API_WANIKANI}`, "Authorization": `Bearer ${process.env["API_WANIKANI"]}`,
"Content-Type": "application/json", "Content-Type": "application.json",
}})).json() as {data: Subject[]}; }})).json() as {data: Subject[]};
const lessons = addStuffToLearn(subjectIdsLessons, summary.data.lessons, subjects.data); const lessons = addStuffToLearn(subjectIdsLessons, summary.data.lessons, subjects.data);
@ -107,10 +107,7 @@ const handler: Handler = async () => {
moreThingsToReviewAt, moreThingsToReviewAt,
}; };
return { return new Response(new Blob([JSON.stringify(info)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(info), }), {status: 200});
};
}; };
export {handler};

View file

@ -1,7 +1,7 @@
import {type Handler} from "@netlify/functions"; import {type AnilistInfo} from "#Infos/Media/Anilist.tsx";
import {type AnilistInfo} from "#Infos/Media/Anilist.js"; import type { Handler } from "..";
const handler: Handler = async () => { export const media_anilist: Handler = async () => {
const anilist = await fetch("https://graphql.anilist.co", { const anilist = await fetch("https://graphql.anilist.co", {
method: "POST", method: "POST",
headers: { headers: {
@ -63,10 +63,7 @@ const handler: Handler = async () => {
anime.updateDate = anime.updateDate.substring(0, anime.updateDate.indexOf("T")); anime.updateDate = anime.updateDate.substring(0, anime.updateDate.indexOf("T"));
anime.endDate = anime.endDate.substring(0, anime.endDate.indexOf("T")); anime.endDate = anime.endDate.substring(0, anime.endDate.indexOf("T"));
return { return new Response(new Blob([JSON.stringify(anime)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(anime), }), {status: 200});
};
}; };
export {handler};

View file

@ -1,8 +1,8 @@
import {type Handler} from "@netlify/functions"; import {type LastfmInfo} from "#Infos/Media/Lastfm.tsx";
import {type LastfmInfo} from "#Infos/Media/Lastfm.js"; import type { Handler } from "..";
const handler: Handler = async () => { export const media_lastfm: Handler = async () => {
const lastfm = await (await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=TTTaevas&api_key=${process.env.API_LASTFM}&format=json&limit=1`)).json() as { const lastfm = await (await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=TTTaevas&api_key=${process.env["API_LASTFM"]}&format=json&limit=1`)).json() as {
recenttracks: { recenttracks: {
track: { track: {
artist: { artist: {
@ -39,10 +39,7 @@ const handler: Handler = async () => {
date: lastfm.recenttracks.track[0].date?.uts ?? String(Date.now()), date: lastfm.recenttracks.track[0].date?.uts ?? String(Date.now()),
}; };
return { return new Response(new Blob([JSON.stringify(track)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(track), }), {status: 200});
};
}; };
export {handler};

View file

@ -1,18 +1,19 @@
import {type Handler} from "@netlify/functions"; import {MongoClient, type InsertOneResult} from "mongodb";
import {InsertOneResult, MongoClient} from "mongodb";
import {API} from "osu-api-v2-js"; import {API} from "osu-api-v2-js";
import type { Handler } from "..";
export interface Token { export interface Token {
access_token: string; access_token: string;
expires: Date; expires: Date;
} }
const handler: Handler = async (req) => { export const token: Handler = async (params) => {
const service = req.queryStringParameters?.service; const service = params.get("service");
if (!service) {return {statusCode: 400};} if (!service) {
return new Response("Bad Request", {status: 400});
}
const client = new MongoClient(process.env.URL_MONGODB!); const client = new MongoClient(process.env["URL_MONGODB"]!);
await client.connect(); await client.connect();
const db = client.db("tokens"); const db = client.db("tokens");
@ -27,14 +28,14 @@ const handler: Handler = async (req) => {
if (!token) { if (!token) {
const collections = await db.listCollections().toArray(); const collections = await db.listCollections().toArray();
if (!collections.find((c) => c.name === service)) {client.close(); return {statusCode: 400};} if (!collections.find((c) => c.name === service)) {client.close(); return new Response("Not Found", {status: 404});}
promises.push(new Promise(async (resolve, reject) => { promises.push(new Promise(async (resolve, reject) => {
console.log(`Setting a new token for ${service}...`); console.log(`Setting a new token for ${service}...`);
let insertion: InsertOneResult; let insertion: InsertOneResult;
if (service === "osu") { if (service === "osu") {
const api = await API.createAsync(11451, process.env.API_OSU!); const api = await API.createAsync(11451, process.env["API_OSU"]!);
insertion = await collection.insertOne({ insertion = await collection.insertOne({
access_token: api.access_token, access_token: api.access_token,
expires: api.expires, expires: api.expires,
@ -47,7 +48,7 @@ const handler: Handler = async (req) => {
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded"
}, },
body: `username=${process.env.USERNAME_UMAMI}&password=${process.env.PASSWORD_UMAMI}` body: `username=${process.env["USERNAME_UMAMI"]}&password=${process.env["PASSWORD_UMAMI"]}`
}); });
const json: {token: string} = await response.json(); const json: {token: string} = await response.json();
@ -90,9 +91,5 @@ const handler: Handler = async (req) => {
await Promise.all(promises); await Promise.all(promises);
void client.close(); void client.close();
return { return new Response(null, {status: 200});
statusCode: 200,
};
}; };
export {handler};

View file

@ -1,10 +1,10 @@
import { UmamiInfo } from "#Infos/Website/Umami.js";
import {type Handler} from "@netlify/functions";
import { MongoClient } from "mongodb"; import { MongoClient } from "mongodb";
import { Token } from "./token.js"; import type { Handler } from "../index.ts";
import type { UmamiInfo } from "#Infos/Website/Umami.tsx";
import type { Token } from "./token.ts";
const handler: Handler = async () => { export const website_umami: Handler = async () => {
const client = new MongoClient(process.env.URL_MONGODB!); const client = new MongoClient(process.env["URL_MONGODB"]!);
await client.connect(); await client.connect();
const db = client.db("tokens"); const db = client.db("tokens");
@ -17,7 +17,7 @@ const handler: Handler = async () => {
const now = new Date(); const now = new Date();
const response = await fetch(`${api_server}/websites/${website_id}/stats?startAt=${Number(new Date("2025"))}&endAt=${Number(now)}`, { const response = await fetch(`${api_server}/websites/${website_id}/stats?startAt=${Number(new Date("2025"))}&endAt=${Number(now)}`, {
headers: { headers: {
"Accept": "application/json", "Accept": "application.json",
"Authorization": `Bearer ${token?.access_token}` "Authorization": `Bearer ${token?.access_token}`
}, },
}); });
@ -33,9 +33,7 @@ const handler: Handler = async () => {
}; };
if (!umami) { if (!umami) {
return { return new Response("Not Found", {status: 404});
statusCode: 404,
};
} }
const info: UmamiInfo = { const info: UmamiInfo = {
@ -45,10 +43,7 @@ const handler: Handler = async () => {
totaltime: umami.totaltime.value totaltime: umami.totaltime.value
}; };
return { return new Response(new Blob([JSON.stringify(info)], {
statusCode: 200, type: "application/json",
body: JSON.stringify(info), }), {status: 200});
};
}; };
export {handler};

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 958 B

After

Width:  |  Height:  |  Size: 958 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Before After
Before After

BIN
bun.lockb

Binary file not shown.

1863
index.css Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1" content="width=device-width, initial-scale=1, maximum-scale=1"
/> />
<link rel="stylesheet" href="/src/App.css"> <link rel="stylesheet" href="./index.css">
<script defer src="https://visitors.taevas.xyz/script.js" data-website-id="f196d626-e609-4841-9a80-0dc60f523ed5"></script> <script defer src="https://visitors.taevas.xyz/script.js" data-website-id="f196d626-e609-4841-9a80-0dc60f523ed5"></script>
<title>Site - taevas.xyz</title> <title>Site - taevas.xyz</title>
</head> </head>
@ -42,6 +42,6 @@
<div id="root"> <div id="root">
</div> </div>
<script type="module" src="/src/App.tsx"></script> <script type="module" src="./App.tsx"></script>
</body> </body>
</html> </html>

103
index.ts Normal file
View file

@ -0,0 +1,103 @@
import { coding_github } from "./api/coding_github";
import { coding_gitlab } from "./api/coding_gitlab";
import { coding_kitsudev } from "./api/coding_kitsudev";
import { fediverse_kitsuclub } from "./api/fediverse_kitsuclub";
import { gaming_osu } from "./api/gaming_osu";
import { gaming_speedruncom } from "./api/gaming_speedruncom";
import { hacking_hackthebox } from "./api/hacking_hackthebox";
import { japanese_wanikani } from "./api/japanese_wanikani";
import { media_anilist } from "./api/media_anilist";
import { media_lastfm } from "./api/media_lastfm";
import { token } from "./api/token";
import { website_umami } from "./api/website_umami";
export type Handler = (req: URLSearchParams) => Promise<Response>;
const api_endpoints: Handler[] = [
coding_github,
coding_gitlab,
coding_kitsudev,
fediverse_kitsuclub,
gaming_osu,
gaming_speedruncom,
hacking_hackthebox,
japanese_wanikani,
media_anilist,
media_lastfm,
token,
website_umami
];
const builds = await Bun.build({
entrypoints: ["./src/App.tsx", "index.css"],
target: "browser",
minify: {
identifiers: true,
syntax: true,
whitespace: true,
},
});
const server = Bun.serve({
port: 8080,
fetch: async (req) => {
const url = new URL(req.url);
const parameters = url.searchParams;
// merciless sanitization
let pathname = url.pathname;
pathname = pathname
.replace(/([^A-Za-z0-9/.-_])/g, "")
.replace(/(?<![a-zA-Z])\.(?![a-zA-Z])/g, "");
if (req.method !== "GET") {
return new Response("Method Not Allowed", { status: 405 });
}
// MAIN PAGE
if (pathname === "/") {
const indexContent = await Bun.file("index.html").text();
return new Response(indexContent, {headers: {"Content-Type": "text/html"}});
}
if (pathname === "/App.tsx" && req.method === "GET") {
return new Response(builds.outputs[0].stream(), {
headers: {
"Content-Type": builds.outputs[0].type,
},
});
};
// EXTERNAL TO MAIN PAGE
if (pathname === "/index.css" && req.method === "GET") {
return new Response(builds.outputs[1].stream(), {
headers: {
"Content-Type": builds.outputs[1].type,
},
});
};
if (pathname.startsWith("/assets")) {
const asset = Bun.file("." + pathname);
return await asset.exists() ? new Response(asset, {status: 200}) : new Response("Not Found", {status: 404});
}
// API
if (pathname.startsWith("/api")) {
for (const endpoint of api_endpoints) {
if (pathname === "/api/" + endpoint.name) {
return await endpoint(parameters);
}
}
}
return new Response("Not Found", {status: 404});
},
});
console.log(`Listening on ${server.hostname}:${server.port}`);

View file

@ -1,13 +0,0 @@
[build]
publish = "dist"
command = "bun run build"
node_bundler = "esbuild"
[dev]
publish = "src"
command = "bun run dev"
targetPort = 5173
node_bundler = "esbuild"
[functions]
node_bundler = "esbuild"

View file

@ -1,26 +0,0 @@
import {type Handler} from "@netlify/functions";
import { Gitlab } from "@gitbeaker/rest";
import {type GitlabInfo} from "#Infos/Coding/GitLab.js";
const handler: Handler = async () => {
const api = new Gitlab({token: process.env.API_GITLAB!});
const gitlab = await api.Events.all({action: "pushed"});
const created_at = gitlab.at(0)?.created_at;
if (typeof created_at !== "string") {
return {
statusCode: 404,
};
}
const activity: GitlabInfo = {
date: created_at.substring(0, created_at.indexOf("T")),
};
return {
statusCode: 200,
body: JSON.stringify(activity),
};
};
export {handler};

View file

@ -1,34 +0,0 @@
import {type Handler} from "@netlify/functions";
import * as osu from "osu-api-v2-js";
import {type OsuInfo} from "#Infos/Gaming/Osu.js";
import {MongoClient} from "mongodb";
import {type Token} from "./token.js";
const handler: Handler = async (req) => {
const client = new MongoClient(process.env.URL_MONGODB!);
await client.connect();
const db = client.db("tokens");
const collection = db.collection<Token>("osu");
const token = await collection.findOne();
void client.close();
const ruleset = Number(req.queryStringParameters?.ruleset);
const api = new osu.API({access_token: token?.access_token});
const profile = await api.getUser(7276846, !isNaN(ruleset) ? ruleset : undefined);
const info: OsuInfo = {
country: profile.country.name,
ranks: {
global: profile.statistics.global_rank ?? 0,
country: profile.statistics.country_rank ?? 0,
},
};
return {
statusCode: 200,
body: JSON.stringify(info),
};
};
export {handler};

View file

@ -1,17 +1,15 @@
{ {
"scripts": { "scripts": {
"dev": "vite", "dev": "bunx bun css && bun --hot index.ts",
"build": "vite build", "css": "bun tailwindcss -i ./src/App.css -o index.css",
"serve": "vite preview",
"lint": "bunx eslint ." "lint": "bunx eslint ."
}, },
"dependencies": { "dependencies": {
"@bachmacintosh/wanikani-api-types": "^1.7.0", "@bachmacintosh/wanikani-api-types": "^1.7.0",
"@carbon/icons-react": "^11.56.0", "@carbon/icons-react": "^11.56.0",
"@gitbeaker/rest": "^42.1.0", "@gitbeaker/rest": "^42.1.0",
"@netlify/functions": "^2.8.2",
"@octokit/rest": "^20.1.2", "@octokit/rest": "^20.1.2",
"mongodb": "^6.14.0", "mongodb": "^6.14.2",
"osu-api-v2-js": "^1.1.1", "osu-api-v2-js": "^1.1.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@ -22,12 +20,13 @@
"@eslint/js": "^9.21.0", "@eslint/js": "^9.21.0",
"@stylistic/eslint-plugin": "^3.1.0", "@stylistic/eslint-plugin": "^3.1.0",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.0.11",
"@types/bun": "latest", "@types/bun": "latest",
"@types/node": "^20.17.22", "@types/node": "^20.17.23",
"@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",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"dotenv": "^16.4.7",
"eslint": "^9.21.0", "eslint": "^9.21.0",
"eslint-config-xo-typescript": "^7.0.0", "eslint-config-xo-typescript": "^7.0.0",
"eslint-plugin-react": "^7.37.4", "eslint-plugin-react": "^7.37.4",
@ -35,14 +34,13 @@
"react-animate-height": "^3.2.3", "react-animate-height": "^3.2.3",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.25.0", "typescript-eslint": "^8.26.0"
"vite": "^5.4.14"
}, },
"imports": { "imports": {
"#Main/*": "./src/Main/*", "#Main/*": "./src/Main/*",
"#Infos/*": "./src/Infos/*", "#Infos/*": "./src/Infos/*",
"#parts/*": "./src/parts/*", "#parts/*": "./src/parts/*",
"#contexts": "./src/contexts.js" "#contexts": "./src/contexts.tsx"
}, },
"type": "module", "type": "module",
"name": "taevas.xyz", "name": "taevas.xyz",

View file

@ -4,7 +4,7 @@
@font-face { @font-face {
font-family: "LexendDeca"; font-family: "LexendDeca";
src: url("/fonts/LexendDeca-Regular.ttf") format("truetype"); src: url("./assets/fonts/LexendDeca-Regular.ttf") format("truetype");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }

View file

@ -1,13 +1,11 @@
import React from "react"; import React from "react";
import {createRoot} from "react-dom/client"; import {createRoot} from "react-dom/client";
import MainContent from "./Main/index.js"; import MainContent from "./Main/index.tsx";
import Infos from "./Infos/index.js"; import Infos from "./Infos/index.tsx";
const container = document.getElementById("root"); const root = createRoot(document.getElementById("root")!);
const root = createRoot(container!);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<div className="App flex text-center bg-gradient-to-tl from-indigo-100 via-sky-300 to-indigo-100 bg-fixed"> <div className="App flex text-center bg-gradient-to-tl from-indigo-100 via-sky-300 to-indigo-100 bg-fixed">

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
export interface GithubInfo { export interface GithubInfo {
public?: { public?: {

View file

@ -1,6 +1,6 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export type GitlabInfo = { export type GitlabInfo = {
date: string; date: string;

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
export type KitsudevInfo = { export type KitsudevInfo = {
name: string name: string

View file

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import GitHub from "./GitHub.js"; import GitHub from "./GitHub.tsx";
import GitLab from "./GitLab.js"; import GitLab from "./GitLab.tsx";
import KitsuDev from "./KitsuDev.js"; import KitsuDev from "./KitsuDev.tsx";
export default function Coding() { export default function Coding() {
const github = <GitHub key={"github"}/>; const github = <GitHub key={"github"}/>;

View file

@ -9,7 +9,7 @@ export default function DataHandler<T extends unknown | undefined>(netlifyFuncti
// Try to get and set data // Try to get and set data
const updateData = async () => { const updateData = async () => {
try { try {
const response = await fetch("/.netlify/functions/" + netlifyFunctionName); const response = await fetch("/api/" + netlifyFunctionName);
if (!response.ok) {throw "failed";}; if (!response.ok) {throw "failed";};
setData(expectData ? await response.json() : true); setData(expectData ? await response.json() : true);
setError(false); setError(false);

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
export type KitsuclubInfo = { export type KitsuclubInfo = {
note_id: string note_id: string

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import KitsuClub from "./KitsuClub.js"; import KitsuClub from "./KitsuClub.tsx";
export default function Hacking() { export default function Hacking() {
const kitsuclub = <KitsuClub key={"kitsuclub"}/>; const kitsuclub = <KitsuClub key={"kitsuclub"}/>;

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import { Ruleset } from "osu-api-v2-js"; import { Ruleset } from "osu-api-v2-js";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export type OsuInfo = { export type OsuInfo = {
country: string; country: string;
@ -28,7 +28,7 @@ export default function Osu(args: {ruleset: Ruleset}) {
try { try {
setElements([ setElements([
<div key={`osu-${ruleset}`} className="flex"> <div key={`osu-${ruleset}`} className="flex">
<img className="m-auto w-16 h-16" alt={`${ruleset} mode logo`} src={`/osu_rulesets/${ruleset}.png`}/> <img className="m-auto w-16 h-16" alt={`${ruleset} mode logo`} src={`./assets/osu_rulesets/${ruleset}.png`}/>
<div className="m-auto"> <div className="m-auto">
<p>Global: <strong>#{data.ranks.global}</strong></p> <p>Global: <strong>#{data.ranks.global}</strong></p>
<p>{data.country}: <strong>#{data.ranks.country}</strong></p> <p>{data.country}: <strong>#{data.ranks.country}</strong></p>

View file

@ -1,8 +1,8 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
import ButtonLink from "#parts/ButtonLink.js"; import ButtonLink from "#parts/ButtonLink.tsx";
export type SpeedruncomInfo = { export type SpeedruncomInfo = {
place: number; place: number;

View file

@ -1,10 +1,10 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import Speedruncom from "./Speedruncom.js"; import Speedruncom from "./Speedruncom.tsx";
import Osu from "./Osu.js"; import Osu from "./Osu.tsx";
import { Ruleset } from "osu-api-v2-js"; import { Ruleset } from "osu-api-v2-js";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export default function RhythmGames() { export default function RhythmGames() {
const {data, error} = DataHandler<boolean>("token?service=osu", 60 * 60 * 8, false); const {data, error} = DataHandler<boolean>("token?service=osu", 60 * 60 * 8, false);

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import ButtonLink from "#parts/ButtonLink.js"; import ButtonLink from "#parts/ButtonLink.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export type HacktheboxInfo = { export type HacktheboxInfo = {
id: string; id: string;

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import Hackthebox from "./Hackthebox.js"; import Hackthebox from "./Hackthebox.tsx";
export default function Hacking() { export default function Hacking() {
const hackthebox = <Hackthebox key={"hackthebox"}/>; const hackthebox = <Hackthebox key={"hackthebox"}/>;

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import { WKLevelProgression, WKReset } from "@bachmacintosh/wanikani-api-types"; import { WKLevelProgression, WKReset } from "@bachmacintosh/wanikani-api-types";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export type WanikaniInfo = { export type WanikaniInfo = {
progression: { progression: {

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import Wanikani from "./Wanikani.js"; import Wanikani from "./Wanikani.tsx";
export default function Japanese() { export default function Japanese() {
const wanikani = <Wanikani key={"wanikani"}/>; const wanikani = <Wanikani key={"wanikani"}/>;

View file

@ -1,7 +1,7 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
export type AnilistInfo = { export type AnilistInfo = {
title: string; title: string;

View file

@ -1,8 +1,8 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import {format} from "timeago.js"; import {format} from "timeago.js";
import Website from "../Website.js"; import Website from "../Website.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export type LastfmInfo = { export type LastfmInfo = {
artist: string; artist: string;

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import Lastfm from "./Lastfm.js"; import Lastfm from "./Lastfm.tsx";
import Anilist from "./Anilist.js"; import Anilist from "./Anilist.tsx";
export default function Media() { export default function Media() {
const lastfm = <Lastfm key={"Lastfm"}/>; const lastfm = <Lastfm key={"Lastfm"}/>;

View file

@ -1,6 +1,6 @@
import React, {useState, useEffect} from "react"; import React, {useState, useEffect} from "react";
import Website from "../Website.js"; import Website from "../Website.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export type UmamiInfo = { export type UmamiInfo = {
pageviews: number pageviews: number

View file

@ -1,7 +1,7 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import Info from "../Info.js"; import Info from "../Info.tsx";
import Umami from "./Umami.js"; import Umami from "./Umami.tsx";
import DataHandler from "#Infos/DataHandler.js"; import DataHandler from "#Infos/DataHandler.tsx";
export default function Website() { export default function Website() {
const {data} = DataHandler<boolean>("token?service=umami", 60 * 60 * 8, false); const {data} = DataHandler<boolean>("token?service=umami", 60 * 60 * 8, false);

View file

@ -1,11 +1,11 @@
import React, {Component} from "react"; import React, {Component} from "react";
import Media from "./Media/index.js"; import Media from "./Media/index.tsx";
// import Hacking from "./Hacking/index.js"; // import Hacking from "./Hacking/index.tsx";
import Coding from "./Coding/index.js"; import Coding from "./Coding/index.tsx";
import Gaming from "./Gaming/index.js"; import Gaming from "./Gaming/index.tsx";
// import Japanese from "./Japanese/index.js"; // import Japanese from "./Japanese/index.tsx";
import Fediverse from "./Fediverse/index.js"; import Fediverse from "./Fediverse/index.tsx";
import Website from "./Website/index.js"; import Website from "./Website/index.tsx";
export default class Infos extends Component { export default class Infos extends Component {
private readonly dragbar = React.createRef<HTMLDivElement>(); private readonly dragbar = React.createRef<HTMLDivElement>();

View file

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import SocialButton from "./SocialButton.js"; import SocialButton from "./SocialButton.tsx";
export default function SocialButtons() { export default function SocialButtons() {
return ( return (
@ -9,7 +9,7 @@ export default function SocialButtons() {
border="border-red-500" border="border-red-500"
rotation="rotate-12" rotation="rotate-12"
link="https://www.youtube.com/@TTTaevas" link="https://www.youtube.com/@TTTaevas"
image="/logos/youtube.svg" image="assets/logos/youtube.svg"
padding="p-1" padding="p-1"
/> />
<SocialButton <SocialButton
@ -17,7 +17,7 @@ export default function SocialButtons() {
border="border-black" border="border-black"
rotation="-rotate-6" rotation="-rotate-6"
link="https://github.com/TTTaevas" link="https://github.com/TTTaevas"
image="/logos/github.svg" image="assets/logos/github.svg"
padding="p-1" padding="p-1"
/> />
<SocialButton <SocialButton
@ -25,14 +25,14 @@ export default function SocialButtons() {
border="border-pink-500" border="border-pink-500"
rotation="-rotate-12" rotation="-rotate-12"
link="https://osu.ppy.sh/users/7276846" link="https://osu.ppy.sh/users/7276846"
image="/logos/osu.svg" image="assets/logos/osu.svg"
/> />
<SocialButton <SocialButton
title="Speedrun.com" title="Speedrun.com"
border="border-yellow-500" border="border-yellow-500"
rotation="rotate-12" rotation="rotate-12"
link="https://www.speedrun.com/users/Taevas" link="https://www.speedrun.com/users/Taevas"
image="/logos/speedrundotcom.png" image="assets/logos/speedrundotcom.png"
padding="p-2" padding="p-2"
/> />
<SocialButton <SocialButton
@ -40,7 +40,7 @@ export default function SocialButtons() {
border="border-cyan-500" border="border-cyan-500"
rotation="rotate-6" rotation="rotate-6"
link="https://anilist.co/user/Taevas/" link="https://anilist.co/user/Taevas/"
image="/logos/anilist.svg" image="assets/logos/anilist.svg"
padding="p-1" padding="p-1"
/> />
<SocialButton <SocialButton
@ -48,7 +48,7 @@ export default function SocialButtons() {
border="border-orange-500" border="border-orange-500"
rotation="-rotate-12" rotation="-rotate-12"
link="https://gitlab.com/TTTaevas" link="https://gitlab.com/TTTaevas"
image="/logos/gitlab.svg" image="assets/logos/gitlab.svg"
padding="p-1" padding="p-1"
/> />
<SocialButton <SocialButton
@ -56,7 +56,7 @@ export default function SocialButtons() {
border="border-red-600" border="border-red-600"
rotation="-rotate-6" rotation="-rotate-6"
link="https://www.last.fm/user/TTTaevas" link="https://www.last.fm/user/TTTaevas"
image="/logos/lastdotfm.png" image="assets/logos/lastdotfm.png"
padding="p-1" padding="p-1"
/> />
</div> </div>

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import TabButton from "./TabButton.js"; import TabButton from "./TabButton.tsx";
import Translatable from "#parts/Translatable.js"; import Translatable from "#parts/Translatable.tsx";
import {type TabDetails, LanguageContext, TabContext} from "#contexts"; import {type TabDetails, LanguageContext, TabContext} from "#contexts";
export default function TabButtons({ export default function TabButtons({

View file

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import AnimateHeight from "react-animate-height"; import AnimateHeight from "react-animate-height";
import TabButtons from "./TabButtons/index.js"; import TabButtons from "./TabButtons/index.tsx";
import SocialButtons from "./SocialButtons/index.js"; import SocialButtons from "./SocialButtons/index.tsx";
import Translatable from "#parts/Translatable.js"; import Translatable from "#parts/Translatable.tsx";
import {type TabDetails, TabContext} from "#contexts"; import {type TabDetails, TabContext} from "#contexts";

View file

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import Tab from "../Tab.js"; import Tab from "../Tab.tsx";
import Translatable from "#parts/Translatable.js"; import Translatable from "#parts/Translatable.tsx";
import {UserProfile} from "@carbon/icons-react"; import {UserProfile} from "@carbon/icons-react";
import {type TabDetails} from "#contexts"; import {type TabDetails} from "#contexts";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
export default function About({ export default function About({
setTabs, setTabs,

View file

@ -1,10 +1,10 @@
import React from "react"; import React from "react";
import Tab from "../Tab.js"; import Tab from "../Tab.tsx";
import {MailAll} from "@carbon/icons-react"; import {MailAll} from "@carbon/icons-react";
import CopyField from "#parts/CopyField.js"; import CopyField from "#parts/CopyField.tsx";
import ButtonLink from "#parts/ButtonLink.js"; import ButtonLink from "#parts/ButtonLink.tsx";
import Translatable from "#parts/Translatable.js"; import Translatable from "#parts/Translatable.tsx";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
import {type TabDetails} from "#contexts"; import {type TabDetails} from "#contexts";
export default function Contact({ export default function Contact({
@ -28,7 +28,7 @@ export default function Contact({
en={<p className="text-center">So, I've decided to go for <Link link="https://matrix.org/" text="the Matrix protocol!"/> Feel free to get in touch with me on this account:</p>} en={<p className="text-center">So, I've decided to go for <Link link="https://matrix.org/" text="the Matrix protocol!"/> Feel free to get in touch with me on this account:</p>}
fr={<p className="text-center">Alors, j'ai opté pour <Link link="https://matrix.org/" text= "le protocole Matrix !"/> N'hésitez pas à entrer en contact avec moi sur ce compte :</p>} fr={<p className="text-center">Alors, j'ai opté pour <Link link="https://matrix.org/" text= "le protocole Matrix !"/> N'hésitez pas à entrer en contact avec moi sur ce compte :</p>}
/> />
<CopyField text="@taevas:matrix.org" imageUrl="/logos/matrix.svg"/> <CopyField text="@taevas:matrix.org" imageUrl="assets/logos/matrix.svg"/>
<Translatable <Translatable
en={<ButtonLink link="https://matrix.to/#/@taevas:matrix.org" text="(matrix.to link)" />} en={<ButtonLink link="https://matrix.to/#/@taevas:matrix.org" text="(matrix.to link)" />}
fr={<ButtonLink link="https://matrix.to/#/@taevas:matrix.org" text="(lien matrix.to)" />} fr={<ButtonLink link="https://matrix.to/#/@taevas:matrix.org" text="(lien matrix.to)" />}

View file

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import Tab from "../Tab.js"; import Tab from "../Tab.tsx";
import Translatable from "#parts/Translatable.js"; import Translatable from "#parts/Translatable.tsx";
import {Devices} from "@carbon/icons-react"; import {Devices} from "@carbon/icons-react";
import {type TabDetails} from "#contexts"; import {type TabDetails} from "#contexts";
import Link from "#parts/Link.js"; import Link from "#parts/Link.tsx";
export default function Projects({ export default function Projects({
setTabs, setTabs,
@ -48,8 +48,8 @@ export default function Projects({
</div> </div>
<div className="border-b-4 py-4"> <div className="border-b-4 py-4">
<Translatable <Translatable
en={<p>Still in early 2023, I've made <b><Link link="https://github.com/TTTaevas/osu-api-v1-js" text="osu-api-v1-js"/>, my first JavaScript (TypeScript) package!</b></p>} en={<p>Still in early 2023, I've made <b><Link link="https://github.com/TTTaevas/osu-api-v1.tsx" text="osu-api-v1.tsx"/>, my first JavaScript (TypeScript) package!</b></p>}
fr={<p>Toujours début 2023, j'ai créé <b><Link link="https://github.com/TTTaevas/osu-api-v1-js" text="osu-api-v1-js"/>, mon premier package JavaScript (TypeScript) !</b></p>} fr={<p>Toujours début 2023, j'ai créé <b><Link link="https://github.com/TTTaevas/osu-api-v1.tsx" text="osu-api-v1.tsx"/>, mon premier package JavaScript (TypeScript) !</b></p>}
/> />
<br/> <br/>
<Translatable <Translatable
@ -86,8 +86,8 @@ export default function Projects({
</div> </div>
<div className="border-b-4 py-4"> <div className="border-b-4 py-4">
<Translatable <Translatable
en={<p>So <Link link="https://github.com/TTTaevas/osu-api-v2-js" text="osu-api-v2-js"/> is something I've had to work on during three different and distinct periods. I started working on it in March 2023 roughly around the time I finished working on osu-api-v1-js, then I took a break and worked on it again in November 2023, only to finally finish it in March 2024.</p>} en={<p>So <Link link="https://github.com/TTTaevas/osu-api-v2-js" text="osu-api-v2-js"/> is something I've had to work on during three different and distinct periods. I started working on it in March 2023 roughly around the time I finished working on osu-api-v1.tsx, then I took a break and worked on it again in November 2023, only to finally finish it in March 2024.</p>}
fr={<p>Alors <Link link="https://github.com/TTTaevas/osu-api-v2-js" text="osu-api-v2-js"/>, c'est quelque chose sur lequel j'ai travailler durant 3 périodes bien différentes, en commençant en Mars 2023 genre quand j'ai fini osu-api-v1-js, puis Novembre 2023 après une pause, puis enfin Mars 2024 après une autre pause.</p>} fr={<p>Alors <Link link="https://github.com/TTTaevas/osu-api-v2-js" text="osu-api-v2-js"/>, c'est quelque chose sur lequel j'ai travailler durant 3 périodes bien différentes, en commençant en Mars 2023 genre quand j'ai fini osu-api-v1.tsx, puis Novembre 2023 après une pause, puis enfin Mars 2024 après une autre pause.</p>}
/> />
<br/> <br/>
<Translatable <Translatable

View file

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import Tab from "../Tab.js"; import Tab from "../Tab.tsx";
import Translatable from "#parts/Translatable.js"; import Translatable from "#parts/Translatable.tsx";
import {UserFavorite} from "@carbon/icons-react"; import {UserFavorite} from "@carbon/icons-react";
import {type TabDetails} from "#contexts"; import {type TabDetails} from "#contexts";
import ButtonLink from "#parts/ButtonLink.js"; import ButtonLink from "#parts/ButtonLink.tsx";
export default function Support({ export default function Support({
setTabs, setTabs,

View file

@ -1,6 +1,6 @@
import React, {Component} from "react"; import React, {Component} from "react";
import AnimateHeight from "react-animate-height"; import AnimateHeight from "react-animate-height";
import type Translatable from "#parts/Translatable.js"; import type Translatable from "#parts/Translatable.tsx";
import {type TabDetails, TabContext} from "#contexts"; import {type TabDetails, TabContext} from "#contexts";
export default class Tab extends Component<{ export default class Tab extends Component<{

View file

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import About from "./About/index.js"; import About from "./About/index.tsx";
import Contact from "./Contact/index.js"; import Contact from "./Contact/index.tsx";
import Projects from "./Projects/index.js"; import Projects from "./Projects/index.tsx";
import Support from "./Support/index.js"; import Support from "./Support/index.tsx";
import {type TabDetails} from "#contexts"; import {type TabDetails} from "#contexts";
export default function Tabs({ export default function Tabs({

View file

@ -1,6 +1,6 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import MainWindow from "./MainWindow/index.js"; import MainWindow from "./MainWindow/index.tsx";
import Tabs from "./Tabs/index.js"; import Tabs from "./Tabs/index.tsx";
import {type TabDetails, LanguageContext, TabContext} from "#contexts"; import {type TabDetails, LanguageContext, TabContext} from "#contexts";
export default function MainContent() { export default function MainContent() {

View file

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Link from "./Link.js"; import Link from "./Link.tsx";
export default function ButtonLink({ export default function ButtonLink({
link, link,

View file

@ -1,11 +1,23 @@
{ {
"compilerOptions": { "compilerOptions": {
"jsx": "react", "lib": ["ESNext", "DOM"],
"lib": ["DOM"], "target": "ESNext",
"target": "ES6", "module": "ESNext",
"module": "NodeNext", "moduleDetection": "force",
"declaration": false, "jsx": "react-jsx",
"outDir": "./dist", "allowJs": true,
"strict": true
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": true
} }
} }

View file

@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})