Skip to content

Commit 37ee216

Browse files
committed
added latest release version
1 parent 4d76749 commit 37ee216

File tree

8 files changed

+120
-182
lines changed

8 files changed

+120
-182
lines changed

src/components/Project.astro

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,25 @@ function getColor(language: string) {
2424
<div
2525
class="relative w-full sm:w-full md:w-[calc(50%-0.75rem)] lg:w-[calc(25%-0.75rem)] p-8 flex flex-col gap-4 bg-(--background-light) border border-(--background-lightest) rounded-lg"
2626
>
27-
<div class="flex gap-2">
28-
<p class="text-xl font-semibold">{name}</p>
29-
{
30-
languages.map((language: string) => (
31-
<p
32-
class="px-2 rounded-lg text-white"
33-
style={`background-color: ${getColor(language)}`}
34-
>
35-
{language}
36-
</p>
37-
))
38-
}
27+
<div class="flex gap-2 justify-between">
28+
<div class="flex gap-2">
29+
<p class="text-xl font-semibold">{name}</p>
30+
{
31+
languages.map((language: string) => (
32+
<p
33+
class="px-2 rounded-lg text-white"
34+
style={`background-color: ${getColor(language)}`}
35+
>
36+
{language}
37+
</p>
38+
))
39+
}
40+
</div>
41+
<p
42+
data-path={path}
43+
class="project-version px-2 rounded-lg text-white bg-green-600"
44+
>
45+
</p>
3946
</div>
4047
<div class="mb-16">
4148
<p>{description}</p>
@@ -60,4 +67,17 @@ function getColor(language: string) {
6067
}
6168
});
6269
});
70+
document.querySelectorAll(".project-version").forEach(async (element) => {
71+
const repo = (element as HTMLElement).dataset.path;
72+
if (repo) {
73+
const res = await fetch("/api/latest-releases");
74+
const data = await res.json();
75+
const version = data[repo];
76+
if (!version) {
77+
element.remove();
78+
return;
79+
}
80+
element.innerHTML = version;
81+
}
82+
});
6383
</script>

src/pages/[project].astro

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
import Layout from "../layouts/Layout.astro";
3+
import About from "../sections/About.astro";
4+
import Contact from "../sections/Contact.astro";
5+
import Projects from "../sections/Projects.astro";
6+
import Team from "../sections/Team.astro";
7+
---
8+
9+
<Layout>
10+
<About />
11+
<Projects />
12+
<Team />
13+
<Contact />
14+
</Layout>

src/pages/api/latestReleases.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { APIRoute } from "astro";
2+
import { REPOSITORIES } from "../../server/repositories";
3+
import { getLatestRelease } from "../../server/githubReleaseCache";
4+
5+
export const GET: APIRoute = async () => {
6+
const result: Record<string, string | null> = {};
7+
8+
for (const repo of REPOSITORIES) result[repo] = await getLatestRelease(repo);
9+
10+
return new Response(JSON.stringify(result), {
11+
headers: { "Content-Type": "application/json" },
12+
});
13+
};

src/pages/contact/api.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/sections/Contact.astro

Lines changed: 0 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -85,132 +85,3 @@
8585
</div>
8686
</div>
8787
</section>
88-
89-
<script>
90-
let turnstileLoaded = false;
91-
let widgetId = null;
92-
93-
const script = document.createElement("script");
94-
script.src =
95-
"https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
96-
script.async = true;
97-
script.defer = true;
98-
script.onload = () => {
99-
turnstileLoaded = true;
100-
renderTurnstile();
101-
};
102-
document.head.appendChild(script);
103-
104-
function renderTurnstile() {
105-
if (!turnstileLoaded || !window.turnstile) {
106-
setTimeout(renderTurnstile, 100);
107-
return;
108-
}
109-
110-
try {
111-
const container = document.getElementById("turnstile-widget");
112-
if (container && !widgetId) {
113-
widgetId = window.turnstile.render("#turnstile-widget", {
114-
sitekey: "0x4AAAAAACJLRahSOte2nhUO",
115-
theme: "dark",
116-
size: "normal",
117-
callback: function (token) {
118-
console.log("Turnstile verified:", token);
119-
},
120-
"error-callback": function () {
121-
console.error("Turnstile error");
122-
showMessage(
123-
"Verification failed. Please refresh the page.",
124-
"error"
125-
);
126-
},
127-
"expired-callback": function () {
128-
console.log("Turnstile expired");
129-
},
130-
});
131-
}
132-
} catch (error) {
133-
console.error("Failed to render Turnstile:", error);
134-
showMessage(
135-
"Could not load verification. Please refresh the page.",
136-
"error"
137-
);
138-
}
139-
}
140-
141-
function initForm() {
142-
const form = document.getElementById("contact-form") as HTMLFormElement;
143-
const submitBtn = form?.querySelector(
144-
'button[type="submit"]'
145-
) as HTMLButtonElement;
146-
147-
if (!form) return;
148-
149-
form.addEventListener("submit", async (e) => {
150-
e.preventDefault();
151-
152-
const token = window.turnstile?.getResponse(widgetId);
153-
154-
if (!token) {
155-
showMessage("Please complete the verification", "error");
156-
return;
157-
}
158-
159-
submitBtn.disabled = true;
160-
submitBtn.textContent = "Sending...";
161-
162-
try {
163-
const formData = new FormData(form);
164-
const data = Object.fromEntries(formData);
165-
166-
const response = await fetch("/api/contact", {
167-
method: "POST",
168-
headers: { "Content-Type": "application/json" },
169-
body: JSON.stringify({ ...data, token }),
170-
});
171-
172-
if (response.ok) {
173-
form.reset();
174-
if (window.turnstile && widgetId) {
175-
window.turnstile.reset(widgetId);
176-
}
177-
showMessage("Message sent successfully!", "success");
178-
} else {
179-
const errorData = await response.json().catch(() => ({}));
180-
showMessage(
181-
errorData.error || "Failed to send message. Please try again.",
182-
"error"
183-
);
184-
}
185-
} catch (error) {
186-
console.error("Form submission error:", error);
187-
showMessage("An error occurred. Please try again.", "error");
188-
} finally {
189-
submitBtn.disabled = false;
190-
submitBtn.textContent = "Send message";
191-
}
192-
});
193-
}
194-
195-
function showMessage(text: string, type: string) {
196-
const messageEl = document.getElementById("form-message");
197-
if (messageEl) {
198-
messageEl.textContent = text;
199-
messageEl.className = `text-sm ${
200-
type === "success" ? "text-green-500" : "text-red-500"
201-
}`;
202-
messageEl.classList.remove("hidden");
203-
setTimeout(() => messageEl.classList.add("hidden"), 5000);
204-
}
205-
}
206-
207-
if (document.readyState === "loading") {
208-
document.addEventListener("DOMContentLoaded", () => {
209-
initForm();
210-
renderTurnstile();
211-
});
212-
} else {
213-
initForm();
214-
renderTurnstile();
215-
}
216-
</script>

src/sections/Projects.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ import Project from "../components/Project.astro";
4848
path="docker-server-manager"
4949
/>
5050
<Project
51-
name="requests4j"
51+
name="servermanager"
5252
languages={["Java"]}
53-
description="A library for requests."
54-
path="requests4j"
53+
description="Manage your minecraft servers."
54+
path="servermanager"
5555
/>
5656
</div>
5757
</section>

src/server/githubReleaseCache.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { REPOSITORIES } from "./repositories";
2+
3+
const TEN_MIN = 10 * 60 * 1000;
4+
5+
type ReleaseCache = {
6+
title: string | null;
7+
fetchedAt: number;
8+
};
9+
10+
const cache = new Map<string, ReleaseCache>();
11+
12+
async function fetchLatestRelease(repo: string) {
13+
const res = await fetch(
14+
`https://api.github.com/repos/Nexoscript/${repo}/releases/latest`,
15+
{
16+
headers: {
17+
Accept: "application/vnd.github+json",
18+
"User-Agent": "astro-app",
19+
},
20+
}
21+
);
22+
23+
if (!res.ok) return;
24+
25+
const json = await res.json();
26+
27+
cache.set(repo, {
28+
title: json.name ?? json.tag_name ?? null,
29+
fetchedAt: Date.now(),
30+
});
31+
}
32+
33+
export async function getLatestRelease(repo: string) {
34+
const entry = cache.get(repo);
35+
36+
if (!entry || Date.now() - entry.fetchedAt > TEN_MIN)
37+
await fetchLatestRelease(repo);
38+
39+
return cache.get(repo)?.title ?? null;
40+
}
41+
42+
/* Background refresh */
43+
setInterval(() => {
44+
for (const repo of REPOSITORIES) fetchLatestRelease(repo);
45+
}, TEN_MIN);
46+
47+
/* Initial fetch */
48+
for (const repo of REPOSITORIES) fetchLatestRelease(repo);

src/server/repositories.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const REPOSITORIES = [
2+
"nexonet-java",
3+
"nexomod",
4+
"nexoengine",
5+
"kryobase",
6+
"smoothcloud",
7+
"javaro",
8+
"docker-server-manager",
9+
"servermanager",
10+
];

0 commit comments

Comments
 (0)