configure board source in typescript
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
import { boardConfig } from "../../../board.config";
|
||||||
|
|
||||||
type GithubContent = {
|
type GithubContent = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -68,12 +69,10 @@ async function fetchDirectory(owner: string, repo: string, path: string, branch?
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET() {
|
||||||
const { searchParams } = new URL(request.url);
|
const branch = boardConfig.branch || undefined;
|
||||||
const repoValue = searchParams.get("repo") || process.env.NEXT_PUBLIC_GITHUB_REPO || null;
|
const path = boardConfig.photosPath;
|
||||||
const branch = searchParams.get("branch") || process.env.NEXT_PUBLIC_GITHUB_BRANCH || undefined;
|
const parsedRepo = parseRepo(boardConfig.repo);
|
||||||
const path = searchParams.get("path") || process.env.NEXT_PUBLIC_GITHUB_PHOTOS_PATH || "";
|
|
||||||
const parsedRepo = parseRepo(repoValue);
|
|
||||||
|
|
||||||
if (!parsedRepo) {
|
if (!parsedRepo) {
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
+8
-72
@@ -131,71 +131,13 @@ h1 {
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sourceBar {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(190px, 1.2fr) minmax(160px, 0.8fr) minmax(130px, 0.55fr) auto;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: end;
|
|
||||||
margin: 34px 0 28px;
|
|
||||||
padding: 14px;
|
|
||||||
border: 2px solid var(--line);
|
|
||||||
background: rgba(12, 15, 12, 0.94);
|
|
||||||
box-shadow: 6px 6px 0 var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sourceBar label {
|
|
||||||
display: grid;
|
|
||||||
gap: 7px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sourceBar span {
|
|
||||||
font-family: "Courier New", monospace;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 800;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sourceBar input {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 46px;
|
|
||||||
border: 2px solid var(--line);
|
|
||||||
border-radius: 0;
|
|
||||||
background: #050605;
|
|
||||||
color: var(--ink);
|
|
||||||
padding: 0 12px;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sourceBar input:focus {
|
|
||||||
box-shadow: 0 0 0 3px var(--acid);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sourceBar button {
|
|
||||||
min-height: 46px;
|
|
||||||
border: 2px solid var(--line);
|
|
||||||
border-radius: 0;
|
|
||||||
background: var(--blood);
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: "Courier New", monospace;
|
|
||||||
font-weight: 900;
|
|
||||||
text-transform: uppercase;
|
|
||||||
transition: transform 160ms ease, box-shadow 160ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sourceBar button:hover {
|
|
||||||
box-shadow: 4px 4px 0 var(--line);
|
|
||||||
transform: translate(-2px, -2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.board {
|
.board {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
gap: 26px;
|
gap: 26px;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
min-height: 240px;
|
min-height: 240px;
|
||||||
padding: 12px 0;
|
padding: 34px 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
@@ -214,24 +156,20 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.photoCard {
|
.photoCard {
|
||||||
--tilt: 0deg;
|
|
||||||
border: 2px solid var(--line);
|
border: 2px solid var(--line);
|
||||||
background: #0d100d;
|
background: #0d100d;
|
||||||
box-shadow: 8px 9px 0 var(--line);
|
box-shadow: 8px 9px 0 var(--line);
|
||||||
transform: rotate(var(--tilt));
|
|
||||||
transition: transform 180ms ease, box-shadow 180ms ease;
|
transition: transform 180ms ease, box-shadow 180ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photoCard:hover {
|
.photoCard:hover {
|
||||||
box-shadow: 12px 13px 0 var(--blood);
|
box-shadow: 12px 13px 0 var(--blood);
|
||||||
transform: rotate(0deg) translate(-3px, -3px);
|
transform: translate(-3px, -3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.photoCard a {
|
.photoCard a {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
overflow: visible;
|
||||||
aspect-ratio: 4 / 5;
|
|
||||||
overflow: hidden;
|
|
||||||
border-bottom: 2px solid var(--line);
|
border-bottom: 2px solid var(--line);
|
||||||
background: #1b2019;
|
background: #1b2019;
|
||||||
}
|
}
|
||||||
@@ -239,8 +177,7 @@ h1 {
|
|||||||
.photoCard img {
|
.photoCard img {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: auto;
|
||||||
object-fit: cover;
|
|
||||||
filter: contrast(1.12) saturate(0.82) brightness(0.86);
|
filter: contrast(1.12) saturate(0.82) brightness(0.86);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,8 +212,7 @@ h1 {
|
|||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero,
|
.hero {
|
||||||
.sourceBar {
|
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,8 +232,8 @@ h1 {
|
|||||||
min-height: 130px;
|
min-height: 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sourceBar button {
|
.board {
|
||||||
width: 100%;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-65
@@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FormEvent, useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import Image from "next/image";
|
|
||||||
|
|
||||||
type ShamePhoto = {
|
type ShamePhoto = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -18,33 +17,7 @@ type PhotoResponse = {
|
|||||||
message: string | null;
|
message: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialRepo = process.env.NEXT_PUBLIC_GITHUB_REPO ?? "";
|
|
||||||
const initialPath = process.env.NEXT_PUBLIC_GITHUB_PHOTOS_PATH ?? "";
|
|
||||||
const initialBranch = process.env.NEXT_PUBLIC_GITHUB_BRANCH ?? "";
|
|
||||||
|
|
||||||
function buildQuery(repo: string, path: string, branch: string) {
|
|
||||||
const query = new URLSearchParams();
|
|
||||||
|
|
||||||
if (repo.trim()) {
|
|
||||||
query.set("repo", repo.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.trim()) {
|
|
||||||
query.set("path", path.trim().replace(/^\/+/, ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (branch.trim()) {
|
|
||||||
query.set("branch", branch.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
return query.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [repo, setRepo] = useState(initialRepo);
|
|
||||||
const [path, setPath] = useState(initialPath);
|
|
||||||
const [branch, setBranch] = useState(initialBranch);
|
|
||||||
const [activeQuery, setActiveQuery] = useState(() => buildQuery(initialRepo, initialPath, initialBranch));
|
|
||||||
const [data, setData] = useState<PhotoResponse | null>(null);
|
const [data, setData] = useState<PhotoResponse | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -57,7 +30,7 @@ export default function Home() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/github-photos?${activeQuery}`, {
|
const response = await fetch("/api/github-photos", {
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
const nextData = (await response.json()) as PhotoResponse;
|
const nextData = (await response.json()) as PhotoResponse;
|
||||||
@@ -81,7 +54,7 @@ export default function Home() {
|
|||||||
loadPhotos();
|
loadPhotos();
|
||||||
|
|
||||||
return () => controller.abort();
|
return () => controller.abort();
|
||||||
}, [activeQuery]);
|
}, []);
|
||||||
|
|
||||||
const repoLabel = data?.repo ?? "GitHub source not set";
|
const repoLabel = data?.repo ?? "GitHub source not set";
|
||||||
const boardStamp = useMemo(() => {
|
const boardStamp = useMemo(() => {
|
||||||
@@ -94,11 +67,6 @@ export default function Home() {
|
|||||||
return `${count} filed`;
|
return `${count} filed`;
|
||||||
}, [data?.photos.length]);
|
}, [data?.photos.length]);
|
||||||
|
|
||||||
function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
||||||
event.preventDefault();
|
|
||||||
setActiveQuery(buildQuery(repo, path, branch));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="shell">
|
<main className="shell">
|
||||||
<section className="hero" aria-labelledby="page-title">
|
<section className="hero" aria-labelledby="page-title">
|
||||||
@@ -114,43 +82,15 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<form className="sourceBar" onSubmit={handleSubmit}>
|
|
||||||
<label>
|
|
||||||
<span>repo</span>
|
|
||||||
<input
|
|
||||||
value={repo}
|
|
||||||
onChange={(event) => setRepo(event.target.value)}
|
|
||||||
spellCheck={false}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<span>folder</span>
|
|
||||||
<input
|
|
||||||
value={path}
|
|
||||||
onChange={(event) => setPath(event.target.value)}
|
|
||||||
spellCheck={false}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<span>branch</span>
|
|
||||||
<input
|
|
||||||
value={branch}
|
|
||||||
onChange={(event) => setBranch(event.target.value)}
|
|
||||||
spellCheck={false}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<button type="submit">Обновить</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<section className="board" aria-live="polite" aria-busy={isLoading}>
|
<section className="board" aria-live="polite" aria-busy={isLoading}>
|
||||||
{isLoading ? <div className="notice">Загружаю фотографии с GitHub...</div> : null}
|
{isLoading ? <div className="notice">Загружаю фотографии с GitHub...</div> : null}
|
||||||
{!isLoading && error ? <div className="notice danger">{error}</div> : null}
|
{!isLoading && error ? <div className="notice danger">{error}</div> : null}
|
||||||
{!isLoading && !error && data?.message ? <div className="notice">{data.message}</div> : null}
|
{!isLoading && !error && data?.message ? <div className="notice">{data.message}</div> : null}
|
||||||
|
|
||||||
{data?.photos.map((photo, index) => (
|
{data?.photos.map((photo, index) => (
|
||||||
<article className="photoCard" key={photo.path} style={{ "--tilt": `${(index % 5) - 2}deg` } as React.CSSProperties}>
|
<article className="photoCard" key={photo.path}>
|
||||||
<a href={photo.htmlUrl} target="_blank" rel="noreferrer" aria-label={`Open ${photo.name} on GitHub`}>
|
<a href={photo.htmlUrl} target="_blank" rel="noreferrer" aria-label={`Open ${photo.name} on GitHub`}>
|
||||||
<Image src={photo.url} alt={photo.name} fill sizes="(max-width: 520px) 100vw, (max-width: 1200px) 33vw, 260px" />
|
<img src={photo.url} alt={photo.name} loading={index < 3 ? "eager" : "lazy"} />
|
||||||
</a>
|
</a>
|
||||||
<div className="caption">
|
<div className="caption">
|
||||||
<span>#{String(index + 1).padStart(2, "0")}</span>
|
<span>#{String(index + 1).padStart(2, "0")}</span>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const boardConfig = {
|
||||||
|
repo: "dexxdbg/pozor",
|
||||||
|
photosPath: "img",
|
||||||
|
branch: "codex/initial-shame-board"
|
||||||
|
} as const;
|
||||||
Reference in New Issue
Block a user