feat: implement user authentication and license management system

- Added schema for users, licenses, and license hostnames in the database.
- Created storage utility for reading and writing JSON files.
- Developed user service for user registration, authentication, and retrieval.
- Implemented authentication middleware to protect routes.
- Built LicenseCard component to display license details.
- Created SiteNav component for navigation with user authentication status.
- Established AuthContext for managing authentication state and actions.
- Developed Home page to display available plugins.
- Created LicenseManager page for managing licenses with forms for creation and verification.
- Implemented PluginDetail page to show detailed information about a specific plugin.
- Added utility functions for date formatting.
This commit is contained in:
2026-02-01 02:20:28 +00:00
parent f4411ffd88
commit 7b0ca40c4f
27 changed files with 2344 additions and 428 deletions

125
src/pages/PluginDetail.jsx Normal file
View File

@@ -0,0 +1,125 @@
import { useEffect, useMemo, useState } from "react";
import { Link, useParams } from "react-router-dom";
export default function PluginDetail() {
const { owner, repo } = useParams();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function loadDetail() {
try {
const response = await fetch(`/api/plugins/${owner}/${repo}`);
if (!response.ok) {
throw new Error("Kon details niet laden");
}
const detail = await response.json();
setData(detail);
} catch (err) {
setError("Laden van plugin details is mislukt.");
} finally {
setLoading(false);
}
}
loadDetail();
}, [owner, repo]);
const manifest = data?.manifest;
const displayName = manifest?.plugin_name || data?.name || repo;
const description = manifest?.description || data?.description;
const author = manifest?.author || "-";
const version = manifest?.version || "-";
const releases = useMemo(() => data?.releases || [], [data]);
const commits = useMemo(() => data?.commits || [], [data]);
return (
<div className="page">
<header className="detail-hero">
<div>
<p className="eyebrow">Plugin details</p>
<h1>{displayName}</h1>
<p className="subtitle">{description}</p>
</div>
<div className="detail-actions">
<Link className="ghost" to="/"> Terug</Link>
{data?.repoUrl && (
<a className="cta" href={data.repoUrl} target="_blank" rel="noreferrer">
GitHub
</a>
)}
</div>
</header>
{loading && <div className="state">Bezig met laden</div>}
{error && <div className="state error">{error}</div>}
{!loading && !error && data && (
<section className="detail-grid">
<div className="card">
<h2>Manifest</h2>
<div className="detail-list">
<div>
<span>Naam</span>
<strong>{displayName}</strong>
</div>
<div>
<span>Versie</span>
<strong>{version}</strong>
</div>
<div>
<span>Auteur</span>
<strong>{author}</strong>
</div>
<div>
<span>Repository</span>
<strong>{data.fullName}</strong>
</div>
</div>
{manifest?.author_url && (
<a className="link" href={manifest.author_url} target="_blank" rel="noreferrer">
Auteur website
</a>
)}
</div>
<div className="card">
<h2>Releases</h2>
{releases.length === 0 && <p>Geen releases gevonden.</p>}
<ul className="list">
{releases.map((release) => (
<li key={release.tag}>
<a href={release.url} target="_blank" rel="noreferrer">
{release.name}
</a>
<span>
{release.publishedAt
? new Date(release.publishedAt).toLocaleDateString("nl-NL")
: "-"}
</span>
</li>
))}
</ul>
</div>
<div className="card">
<h2>Recente commits</h2>
{commits.length === 0 && <p>Geen commits gevonden.</p>}
<ul className="list">
{commits.map((commit) => (
<li key={commit.sha}>
<a href={commit.url} target="_blank" rel="noreferrer">
{commit.message?.split("\n")[0] || commit.sha.slice(0, 7)}
</a>
<span>{commit.author || "-"}</span>
</li>
))}
</ul>
</div>
</section>
)}
</div>
);
}