Remove Netlify, make an API server
2
.gitignore
vendored
|
@ -1,4 +1,4 @@
|
|||
.netlify
|
||||
node_modules
|
||||
dist
|
||||
*.log
|
||||
.env
|
||||
|
|
12
README.md
|
@ -1,5 +1,3 @@
|
|||
[](https://taevas.xyz)
|
||||
|
||||
# taevas.xyz
|
||||
|
||||
My personal website!
|
||||
|
@ -7,18 +5,16 @@ My personal website!
|
|||
## Build and develop
|
||||
|
||||
```bash
|
||||
bun install --global netlify-cli
|
||||
bun i --ignore-scripts
|
||||
netlify dev
|
||||
bun i
|
||||
bun dev
|
||||
```
|
||||
|
||||
## 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):**
|
||||
|
||||
```bash
|
||||
netlify env:set IBM_TELEMETRY_DISABLED true
|
||||
```
|
||||
Set the environment variable IBM_TELEMETRY_DISABLED to 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:
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
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 () => {
|
||||
const octokit = new Octokit({auth: process.env.API_GITHUB});
|
||||
export const coding_github: Handler = async () => {
|
||||
const octokit = new Octokit({auth: process.env["API_GITHUB"]});
|
||||
const github = await octokit.rest.activity.listEventsForAuthenticatedUser({username: "TTTaevas"});
|
||||
|
||||
const publicPush = github.data.find((e) => (e.type === "PushEvent" || e.type === "PullRequestEvent") && e.public);
|
||||
|
@ -19,10 +19,7 @@ const handler: Handler = async () => {
|
|||
} : undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(info),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(info)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
21
api/coding_gitlab.ts
Normal 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});
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import { type KitsudevInfo } from "#Infos/Coding/KitsuDev.js";
|
||||
import { type KitsudevInfo } from "#Infos/Coding/KitsuDev.tsx";
|
||||
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 [{
|
||||
repo: {
|
||||
full_name: string
|
||||
|
@ -16,10 +16,7 @@ const handler: Handler = async () => {
|
|||
date: kitsudev[0].created
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(info),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(info)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,11 +1,11 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import { KitsuclubInfo } from "#Infos/Fediverse/KitsuClub.js";
|
||||
import { type KitsuclubInfo } from "#Infos/Fediverse/KitsuClub.tsx";
|
||||
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", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${process.env.API_KITSUCLUB}`,
|
||||
"Authorization": `Bearer ${process.env["API_KITSUCLUB"]}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
|
@ -42,9 +42,7 @@ const handler: Handler = async () => {
|
|||
|
||||
const details = kitsuclub.at(Math.max(0, kitsuclub.length - 1));
|
||||
if (!details) {
|
||||
return {
|
||||
statusCode: 404,
|
||||
};
|
||||
return new Response("Not Found", {status: 404});
|
||||
}
|
||||
|
||||
let scan_text = details.text;
|
||||
|
@ -89,10 +87,7 @@ const handler: Handler = async () => {
|
|||
})
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(activity),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(activity)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
32
api/gaming_osu.ts
Normal 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});
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import {type SpeedruncomInfo} from "#Infos/Gaming/Speedruncom.js";
|
||||
import {type SpeedruncomInfo} from "#Infos/Gaming/Speedruncom.tsx";
|
||||
import type { Handler } from "..";
|
||||
|
||||
interface Runs {
|
||||
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
|
||||
const speedruncom = await (await fetch("https://www.speedrun.com/api/v1/users/j03v45mj/personal-bests")).json() as Runs;
|
||||
const data = speedruncom.data.at(0);
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
statusCode: 404,
|
||||
};
|
||||
return new Response("Not Found", {status: 404});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(run),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(run)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
// https://gist.github.com/vankasteelj/74ab7793133f4b257ea3
|
||||
|
@ -92,5 +89,3 @@ function sec2time(timeInSeconds: number) {
|
|||
const milliseconds = Number(time.toString().slice(-3));
|
||||
return pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3);
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,7 +1,7 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import {type HacktheboxInfo} from "#Infos/Hacking/Hackthebox.js";
|
||||
import {type HacktheboxInfo} from "#Infos/Hacking/Hackthebox.tsx";
|
||||
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 {
|
||||
profile: {
|
||||
activity: HacktheboxInfo[];
|
||||
|
@ -10,19 +10,13 @@ const handler: Handler = async () => {
|
|||
|
||||
const pwn = hackthebox.profile.activity.find((a: HacktheboxInfo) => a?.object_type === "machine");
|
||||
if (!pwn) {
|
||||
return {
|
||||
statusCode: 404,
|
||||
body: "",
|
||||
};
|
||||
return new Response("Not Found", {status: 404});
|
||||
}
|
||||
|
||||
pwn.machine_avatar = `https://www.hackthebox.com${pwn.machine_avatar}`;
|
||||
pwn.date = pwn.date.substring(0, pwn.date.indexOf("T"));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(pwn),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(pwn)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,6 +1,6 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import {type WanikaniInfo} from "#Infos/Japanese/Wanikani.js";
|
||||
import { WKLevelProgression, WKResetCollection, WKSummary } from "@bachmacintosh/wanikani-api-types";
|
||||
import {type WanikaniInfo} from "#Infos/Japanese/Wanikani.tsx";
|
||||
import type { WKLevelProgression, WKResetCollection, WKSummary } from "@bachmacintosh/wanikani-api-types";
|
||||
import type { Handler } from "..";
|
||||
|
||||
interface Subject {
|
||||
id: number;
|
||||
|
@ -48,7 +48,7 @@ function addStuffToLearn(ids: number[], data: {available_at: string; subject_ids
|
|||
return arr;
|
||||
}
|
||||
|
||||
const handler: Handler = async () => {
|
||||
export const japanese_wanikani: Handler = async () => {
|
||||
const urlsToRequest = [
|
||||
"https://api.wanikani.com/v2/level_progressions",
|
||||
"https://api.wanikani.com/v2/resets",
|
||||
|
@ -56,8 +56,8 @@ const handler: Handler = async () => {
|
|||
];
|
||||
const toRequest = urlsToRequest.map((url) => new Promise(async (resolve) => {
|
||||
const response = await fetch(url, {headers: {
|
||||
"Authorization": `Bearer ${process.env.API_WANIKANI}`,
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${process.env["API_WANIKANI"]}`,
|
||||
"Content-Type": "application.json",
|
||||
}});
|
||||
resolve(await response.json());
|
||||
}));
|
||||
|
@ -92,8 +92,8 @@ const handler: Handler = async () => {
|
|||
|
||||
const subjectIdsAll = subjectIdsLessons.concat(subjectIdsReviews);
|
||||
const subjects = await (await fetch(`https://api.wanikani.com/v2/subjects?ids=${subjectIdsAll.toString()}`, {headers: {
|
||||
"Authorization": `Bearer ${process.env.API_WANIKANI}`,
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${process.env["API_WANIKANI"]}`,
|
||||
"Content-Type": "application.json",
|
||||
}})).json() as {data: Subject[]};
|
||||
|
||||
const lessons = addStuffToLearn(subjectIdsLessons, summary.data.lessons, subjects.data);
|
||||
|
@ -107,10 +107,7 @@ const handler: Handler = async () => {
|
|||
moreThingsToReviewAt,
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(info),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(info)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,7 +1,7 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import {type AnilistInfo} from "#Infos/Media/Anilist.js";
|
||||
import {type AnilistInfo} from "#Infos/Media/Anilist.tsx";
|
||||
import type { Handler } from "..";
|
||||
|
||||
const handler: Handler = async () => {
|
||||
export const media_anilist: Handler = async () => {
|
||||
const anilist = await fetch("https://graphql.anilist.co", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -63,10 +63,7 @@ const handler: Handler = async () => {
|
|||
anime.updateDate = anime.updateDate.substring(0, anime.updateDate.indexOf("T"));
|
||||
anime.endDate = anime.endDate.substring(0, anime.endDate.indexOf("T"));
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(anime),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(anime)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,8 +1,8 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import {type LastfmInfo} from "#Infos/Media/Lastfm.js";
|
||||
import {type LastfmInfo} from "#Infos/Media/Lastfm.tsx";
|
||||
import type { Handler } from "..";
|
||||
|
||||
const handler: 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 {
|
||||
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 {
|
||||
recenttracks: {
|
||||
track: {
|
||||
artist: {
|
||||
|
@ -39,10 +39,7 @@ const handler: Handler = async () => {
|
|||
date: lastfm.recenttracks.track[0].date?.uts ?? String(Date.now()),
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(track),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(track)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,18 +1,19 @@
|
|||
import {type Handler} from "@netlify/functions";
|
||||
import {InsertOneResult, MongoClient} from "mongodb";
|
||||
|
||||
import {MongoClient, type InsertOneResult} from "mongodb";
|
||||
import {API} from "osu-api-v2-js";
|
||||
import type { Handler } from "..";
|
||||
|
||||
export interface Token {
|
||||
access_token: string;
|
||||
expires: Date;
|
||||
}
|
||||
|
||||
const handler: Handler = async (req) => {
|
||||
const service = req.queryStringParameters?.service;
|
||||
if (!service) {return {statusCode: 400};}
|
||||
export const token: Handler = async (params) => {
|
||||
const service = params.get("service");
|
||||
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();
|
||||
|
||||
const db = client.db("tokens");
|
||||
|
@ -27,14 +28,14 @@ const handler: Handler = async (req) => {
|
|||
|
||||
if (!token) {
|
||||
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) => {
|
||||
console.log(`Setting a new token for ${service}...`);
|
||||
let insertion: InsertOneResult;
|
||||
|
||||
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({
|
||||
access_token: api.access_token,
|
||||
expires: api.expires,
|
||||
|
@ -47,7 +48,7 @@ const handler: Handler = async (req) => {
|
|||
headers: {
|
||||
"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();
|
||||
|
||||
|
@ -90,9 +91,5 @@ const handler: Handler = async (req) => {
|
|||
await Promise.all(promises);
|
||||
void client.close();
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
};
|
||||
return new Response(null, {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
|
@ -1,10 +1,10 @@
|
|||
import { UmamiInfo } from "#Infos/Website/Umami.js";
|
||||
import {type Handler} from "@netlify/functions";
|
||||
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 () => {
|
||||
const client = new MongoClient(process.env.URL_MONGODB!);
|
||||
export const website_umami: Handler = async () => {
|
||||
const client = new MongoClient(process.env["URL_MONGODB"]!);
|
||||
await client.connect();
|
||||
|
||||
const db = client.db("tokens");
|
||||
|
@ -17,7 +17,7 @@ const handler: Handler = async () => {
|
|||
const now = new Date();
|
||||
const response = await fetch(`${api_server}/websites/${website_id}/stats?startAt=${Number(new Date("2025"))}&endAt=${Number(now)}`, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Accept": "application.json",
|
||||
"Authorization": `Bearer ${token?.access_token}`
|
||||
},
|
||||
});
|
||||
|
@ -33,9 +33,7 @@ const handler: Handler = async () => {
|
|||
};
|
||||
|
||||
if (!umami) {
|
||||
return {
|
||||
statusCode: 404,
|
||||
};
|
||||
return new Response("Not Found", {status: 404});
|
||||
}
|
||||
|
||||
const info: UmamiInfo = {
|
||||
|
@ -45,10 +43,7 @@ const handler: Handler = async () => {
|
|||
totaltime: umami.totaltime.value
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(info),
|
||||
};
|
||||
return new Response(new Blob([JSON.stringify(info)], {
|
||||
type: "application/json",
|
||||
}), {status: 200});
|
||||
};
|
||||
|
||||
export {handler};
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 958 B After Width: | Height: | Size: 958 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 955 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
BIN
bun.lockb
|
@ -6,7 +6,7 @@
|
|||
name="viewport"
|
||||
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>
|
||||
<title>Site - taevas.xyz</title>
|
||||
</head>
|
||||
|
@ -42,6 +42,6 @@
|
|||
<div id="root">
|
||||
|
||||
</div>
|
||||
<script type="module" src="/src/App.tsx"></script>
|
||||
<script type="module" src="./App.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
103
index.ts
Normal 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}`);
|
13
netlify.toml
|
@ -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"
|
|
@ -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};
|
|
@ -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};
|
18
package.json
|
@ -1,17 +1,15 @@
|
|||
{
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"dev": "bunx bun css && bun --hot index.ts",
|
||||
"css": "bun tailwindcss -i ./src/App.css -o index.css",
|
||||
"lint": "bunx eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@bachmacintosh/wanikani-api-types": "^1.7.0",
|
||||
"@carbon/icons-react": "^11.56.0",
|
||||
"@gitbeaker/rest": "^42.1.0",
|
||||
"@netlify/functions": "^2.8.2",
|
||||
"@octokit/rest": "^20.1.2",
|
||||
"mongodb": "^6.14.0",
|
||||
"mongodb": "^6.14.2",
|
||||
"osu-api-v2-js": "^1.1.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
@ -22,12 +20,13 @@
|
|||
"@eslint/js": "^9.21.0",
|
||||
"@stylistic/eslint-plugin": "^3.1.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.0.11",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^20.17.22",
|
||||
"@types/node": "^20.17.23",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-xo-typescript": "^7.0.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
|
@ -35,14 +34,13 @@
|
|||
"react-animate-height": "^3.2.3",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.25.0",
|
||||
"vite": "^5.4.14"
|
||||
"typescript-eslint": "^8.26.0"
|
||||
},
|
||||
"imports": {
|
||||
"#Main/*": "./src/Main/*",
|
||||
"#Infos/*": "./src/Infos/*",
|
||||
"#parts/*": "./src/parts/*",
|
||||
"#contexts": "./src/contexts.js"
|
||||
"#contexts": "./src/contexts.tsx"
|
||||
},
|
||||
"type": "module",
|
||||
"name": "taevas.xyz",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
@font-face {
|
||||
font-family: "LexendDeca";
|
||||
src: url("/fonts/LexendDeca-Regular.ttf") format("truetype");
|
||||
src: url("./assets/fonts/LexendDeca-Regular.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import React from "react";
|
||||
import {createRoot} from "react-dom/client";
|
||||
|
||||
import MainContent from "./Main/index.js";
|
||||
import Infos from "./Infos/index.js";
|
||||
import MainContent from "./Main/index.tsx";
|
||||
import Infos from "./Infos/index.tsx";
|
||||
|
||||
|
||||
const container = document.getElementById("root");
|
||||
const root = createRoot(container!);
|
||||
|
||||
const root = createRoot(document.getElementById("root")!);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<div className="App flex text-center bg-gradient-to-tl from-indigo-100 via-sky-300 to-indigo-100 bg-fixed">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
|
||||
export interface GithubInfo {
|
||||
public?: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export type GitlabInfo = {
|
||||
date: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
|
||||
export type KitsudevInfo = {
|
||||
name: string
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from "react";
|
||||
import Info from "../Info.js";
|
||||
import GitHub from "./GitHub.js";
|
||||
import GitLab from "./GitLab.js";
|
||||
import KitsuDev from "./KitsuDev.js";
|
||||
import Info from "../Info.tsx";
|
||||
import GitHub from "./GitHub.tsx";
|
||||
import GitLab from "./GitLab.tsx";
|
||||
import KitsuDev from "./KitsuDev.tsx";
|
||||
|
||||
export default function Coding() {
|
||||
const github = <GitHub key={"github"}/>;
|
||||
|
|
|
@ -9,7 +9,7 @@ export default function DataHandler<T extends unknown | undefined>(netlifyFuncti
|
|||
// Try to get and set data
|
||||
const updateData = async () => {
|
||||
try {
|
||||
const response = await fetch("/.netlify/functions/" + netlifyFunctionName);
|
||||
const response = await fetch("/api/" + netlifyFunctionName);
|
||||
if (!response.ok) {throw "failed";};
|
||||
setData(expectData ? await response.json() : true);
|
||||
setError(false);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
|
||||
export type KitsuclubInfo = {
|
||||
note_id: string
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import Info from "../Info.js";
|
||||
import KitsuClub from "./KitsuClub.js";
|
||||
import Info from "../Info.tsx";
|
||||
import KitsuClub from "./KitsuClub.tsx";
|
||||
|
||||
export default function Hacking() {
|
||||
const kitsuclub = <KitsuClub key={"kitsuclub"}/>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import Website from "../Website.tsx";
|
||||
import { Ruleset } from "osu-api-v2-js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export type OsuInfo = {
|
||||
country: string;
|
||||
|
@ -28,7 +28,7 @@ export default function Osu(args: {ruleset: Ruleset}) {
|
|||
try {
|
||||
setElements([
|
||||
<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">
|
||||
<p>Global: <strong>#{data.ranks.global}</strong></p>
|
||||
<p>{data.country}: <strong>#{data.ranks.country}</strong></p>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
import ButtonLink from "#parts/ButtonLink.tsx";
|
||||
|
||||
export type SpeedruncomInfo = {
|
||||
place: number;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, {useEffect, useState} from "react";
|
||||
import Info from "../Info.js";
|
||||
import Info from "../Info.tsx";
|
||||
|
||||
import Speedruncom from "./Speedruncom.js";
|
||||
import Osu from "./Osu.js";
|
||||
import Speedruncom from "./Speedruncom.tsx";
|
||||
import Osu from "./Osu.tsx";
|
||||
import { Ruleset } from "osu-api-v2-js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export default function RhythmGames() {
|
||||
const {data, error} = DataHandler<boolean>("token?service=osu", 60 * 60 * 8, false);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Website from "../Website.tsx";
|
||||
import ButtonLink from "#parts/ButtonLink.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export type HacktheboxInfo = {
|
||||
id: string;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import Info from "../Info.js";
|
||||
import Hackthebox from "./Hackthebox.js";
|
||||
import Info from "../Info.tsx";
|
||||
import Hackthebox from "./Hackthebox.tsx";
|
||||
|
||||
export default function Hacking() {
|
||||
const hackthebox = <Hackthebox key={"hackthebox"}/>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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 DataHandler from "#Infos/DataHandler.js";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export type WanikaniInfo = {
|
||||
progression: {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import Info from "../Info.js";
|
||||
import Wanikani from "./Wanikani.js";
|
||||
import Info from "../Info.tsx";
|
||||
import Wanikani from "./Wanikani.tsx";
|
||||
|
||||
export default function Japanese() {
|
||||
const wanikani = <Wanikani key={"wanikani"}/>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
|
||||
export type AnilistInfo = {
|
||||
title: string;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import {format} from "timeago.js";
|
||||
import Website from "../Website.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Website from "../Website.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export type LastfmInfo = {
|
||||
artist: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
import Info from "../Info.js";
|
||||
import Lastfm from "./Lastfm.js";
|
||||
import Anilist from "./Anilist.js";
|
||||
import Info from "../Info.tsx";
|
||||
import Lastfm from "./Lastfm.tsx";
|
||||
import Anilist from "./Anilist.tsx";
|
||||
|
||||
export default function Media() {
|
||||
const lastfm = <Lastfm key={"Lastfm"}/>;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {useState, useEffect} from "react";
|
||||
import Website from "../Website.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Website from "../Website.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export type UmamiInfo = {
|
||||
pageviews: number
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {useEffect, useState} from "react";
|
||||
import Info from "../Info.js";
|
||||
import Umami from "./Umami.js";
|
||||
import DataHandler from "#Infos/DataHandler.js";
|
||||
import Info from "../Info.tsx";
|
||||
import Umami from "./Umami.tsx";
|
||||
import DataHandler from "#Infos/DataHandler.tsx";
|
||||
|
||||
export default function Website() {
|
||||
const {data} = DataHandler<boolean>("token?service=umami", 60 * 60 * 8, false);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, {Component} from "react";
|
||||
import Media from "./Media/index.js";
|
||||
// import Hacking from "./Hacking/index.js";
|
||||
import Coding from "./Coding/index.js";
|
||||
import Gaming from "./Gaming/index.js";
|
||||
// import Japanese from "./Japanese/index.js";
|
||||
import Fediverse from "./Fediverse/index.js";
|
||||
import Website from "./Website/index.js";
|
||||
import Media from "./Media/index.tsx";
|
||||
// import Hacking from "./Hacking/index.tsx";
|
||||
import Coding from "./Coding/index.tsx";
|
||||
import Gaming from "./Gaming/index.tsx";
|
||||
// import Japanese from "./Japanese/index.tsx";
|
||||
import Fediverse from "./Fediverse/index.tsx";
|
||||
import Website from "./Website/index.tsx";
|
||||
|
||||
export default class Infos extends Component {
|
||||
private readonly dragbar = React.createRef<HTMLDivElement>();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import SocialButton from "./SocialButton.js";
|
||||
import SocialButton from "./SocialButton.tsx";
|
||||
|
||||
export default function SocialButtons() {
|
||||
return (
|
||||
|
@ -9,7 +9,7 @@ export default function SocialButtons() {
|
|||
border="border-red-500"
|
||||
rotation="rotate-12"
|
||||
link="https://www.youtube.com/@TTTaevas"
|
||||
image="/logos/youtube.svg"
|
||||
image="assets/logos/youtube.svg"
|
||||
padding="p-1"
|
||||
/>
|
||||
<SocialButton
|
||||
|
@ -17,7 +17,7 @@ export default function SocialButtons() {
|
|||
border="border-black"
|
||||
rotation="-rotate-6"
|
||||
link="https://github.com/TTTaevas"
|
||||
image="/logos/github.svg"
|
||||
image="assets/logos/github.svg"
|
||||
padding="p-1"
|
||||
/>
|
||||
<SocialButton
|
||||
|
@ -25,14 +25,14 @@ export default function SocialButtons() {
|
|||
border="border-pink-500"
|
||||
rotation="-rotate-12"
|
||||
link="https://osu.ppy.sh/users/7276846"
|
||||
image="/logos/osu.svg"
|
||||
image="assets/logos/osu.svg"
|
||||
/>
|
||||
<SocialButton
|
||||
title="Speedrun.com"
|
||||
border="border-yellow-500"
|
||||
rotation="rotate-12"
|
||||
link="https://www.speedrun.com/users/Taevas"
|
||||
image="/logos/speedrundotcom.png"
|
||||
image="assets/logos/speedrundotcom.png"
|
||||
padding="p-2"
|
||||
/>
|
||||
<SocialButton
|
||||
|
@ -40,7 +40,7 @@ export default function SocialButtons() {
|
|||
border="border-cyan-500"
|
||||
rotation="rotate-6"
|
||||
link="https://anilist.co/user/Taevas/"
|
||||
image="/logos/anilist.svg"
|
||||
image="assets/logos/anilist.svg"
|
||||
padding="p-1"
|
||||
/>
|
||||
<SocialButton
|
||||
|
@ -48,7 +48,7 @@ export default function SocialButtons() {
|
|||
border="border-orange-500"
|
||||
rotation="-rotate-12"
|
||||
link="https://gitlab.com/TTTaevas"
|
||||
image="/logos/gitlab.svg"
|
||||
image="assets/logos/gitlab.svg"
|
||||
padding="p-1"
|
||||
/>
|
||||
<SocialButton
|
||||
|
@ -56,7 +56,7 @@ export default function SocialButtons() {
|
|||
border="border-red-600"
|
||||
rotation="-rotate-6"
|
||||
link="https://www.last.fm/user/TTTaevas"
|
||||
image="/logos/lastdotfm.png"
|
||||
image="assets/logos/lastdotfm.png"
|
||||
padding="p-1"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
import TabButton from "./TabButton.js";
|
||||
import Translatable from "#parts/Translatable.js";
|
||||
import TabButton from "./TabButton.tsx";
|
||||
import Translatable from "#parts/Translatable.tsx";
|
||||
import {type TabDetails, LanguageContext, TabContext} from "#contexts";
|
||||
|
||||
export default function TabButtons({
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import AnimateHeight from "react-animate-height";
|
||||
|
||||
import TabButtons from "./TabButtons/index.js";
|
||||
import SocialButtons from "./SocialButtons/index.js";
|
||||
import Translatable from "#parts/Translatable.js";
|
||||
import TabButtons from "./TabButtons/index.tsx";
|
||||
import SocialButtons from "./SocialButtons/index.tsx";
|
||||
import Translatable from "#parts/Translatable.tsx";
|
||||
|
||||
import {type TabDetails, TabContext} from "#contexts";
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import Tab from "../Tab.js";
|
||||
import Translatable from "#parts/Translatable.js";
|
||||
import Tab from "../Tab.tsx";
|
||||
import Translatable from "#parts/Translatable.tsx";
|
||||
import {UserProfile} from "@carbon/icons-react";
|
||||
import {type TabDetails} from "#contexts";
|
||||
import Link from "#parts/Link.js";
|
||||
import Link from "#parts/Link.tsx";
|
||||
|
||||
export default function About({
|
||||
setTabs,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from "react";
|
||||
import Tab from "../Tab.js";
|
||||
import Tab from "../Tab.tsx";
|
||||
import {MailAll} from "@carbon/icons-react";
|
||||
import CopyField from "#parts/CopyField.js";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import Translatable from "#parts/Translatable.js";
|
||||
import Link from "#parts/Link.js";
|
||||
import CopyField from "#parts/CopyField.tsx";
|
||||
import ButtonLink from "#parts/ButtonLink.tsx";
|
||||
import Translatable from "#parts/Translatable.tsx";
|
||||
import Link from "#parts/Link.tsx";
|
||||
import {type TabDetails} from "#contexts";
|
||||
|
||||
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>}
|
||||
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
|
||||
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)" />}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import Tab from "../Tab.js";
|
||||
import Translatable from "#parts/Translatable.js";
|
||||
import Tab from "../Tab.tsx";
|
||||
import Translatable from "#parts/Translatable.tsx";
|
||||
import {Devices} from "@carbon/icons-react";
|
||||
import {type TabDetails} from "#contexts";
|
||||
import Link from "#parts/Link.js";
|
||||
import Link from "#parts/Link.tsx";
|
||||
|
||||
export default function Projects({
|
||||
setTabs,
|
||||
|
@ -48,8 +48,8 @@ export default function Projects({
|
|||
</div>
|
||||
<div className="border-b-4 py-4">
|
||||
<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>}
|
||||
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>}
|
||||
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.tsx" text="osu-api-v1.tsx"/>, mon premier package JavaScript (TypeScript) !</b></p>}
|
||||
/>
|
||||
<br/>
|
||||
<Translatable
|
||||
|
@ -86,8 +86,8 @@ export default function Projects({
|
|||
</div>
|
||||
<div className="border-b-4 py-4">
|
||||
<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>}
|
||||
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 dû 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>}
|
||||
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 dû 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/>
|
||||
<Translatable
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from "react";
|
||||
import Tab from "../Tab.js";
|
||||
import Translatable from "#parts/Translatable.js";
|
||||
import Tab from "../Tab.tsx";
|
||||
import Translatable from "#parts/Translatable.tsx";
|
||||
import {UserFavorite} from "@carbon/icons-react";
|
||||
import {type TabDetails} from "#contexts";
|
||||
import ButtonLink from "#parts/ButtonLink.js";
|
||||
import ButtonLink from "#parts/ButtonLink.tsx";
|
||||
|
||||
export default function Support({
|
||||
setTabs,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {Component} from "react";
|
||||
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";
|
||||
|
||||
export default class Tab extends Component<{
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React from "react";
|
||||
import About from "./About/index.js";
|
||||
import Contact from "./Contact/index.js";
|
||||
import Projects from "./Projects/index.js";
|
||||
import Support from "./Support/index.js";
|
||||
import About from "./About/index.tsx";
|
||||
import Contact from "./Contact/index.tsx";
|
||||
import Projects from "./Projects/index.tsx";
|
||||
import Support from "./Support/index.tsx";
|
||||
import {type TabDetails} from "#contexts";
|
||||
|
||||
export default function Tabs({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {useEffect, useState} from "react";
|
||||
import MainWindow from "./MainWindow/index.js";
|
||||
import Tabs from "./Tabs/index.js";
|
||||
import MainWindow from "./MainWindow/index.tsx";
|
||||
import Tabs from "./Tabs/index.tsx";
|
||||
import {type TabDetails, LanguageContext, TabContext} from "#contexts";
|
||||
|
||||
export default function MainContent() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import Link from "./Link.js";
|
||||
import Link from "./Link.tsx";
|
||||
|
||||
export default function ButtonLink({
|
||||
link,
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"lib": ["DOM"],
|
||||
"target": "ES6",
|
||||
"module": "NodeNext",
|
||||
"declaration": false,
|
||||
"outDir": "./dist",
|
||||
"strict": true
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
|