Skip to content

Commit 5fbfb8d

Browse files
committed
feat: Add Lab page
1 parent bc992ee commit 5fbfb8d

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
run: npm ci
2323

2424
- name: Build SvelteKit app
25+
env:
26+
VITE_GITHUB_TOKEN: ${{ secrets.VITE_GITHUB_TOKEN }}
2527
run: npm run build
2628

2729
- name: Add .nojekyll & CNAME

src/routes/lab/+page.svelte

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<script>
2+
import { onMount } from 'svelte';
3+
4+
let issues = [];
5+
let loading = true;
6+
let error = null;
7+
8+
let title = '';
9+
let description = '';
10+
let submitting = false;
11+
let submitError = null;
12+
13+
const GITHUB_TOKEN = import.meta.env.VITE_GITHUB_TOKEN;
14+
const OWNER = 'pyrsuit';
15+
const REPO = 'pyrsuit-dev-blog';
16+
17+
async function fetchIssues() {
18+
loading = true;
19+
try {
20+
const res = await fetch(`https://api.github.com/repos/${OWNER}/${REPO}/issues?per_page=10`, {
21+
headers: { Authorization: `Bearer ${GITHUB_TOKEN}` }
22+
});
23+
if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
24+
issues = await res.json();
25+
error = null;
26+
} catch (e) {
27+
error = e.message;
28+
} finally {
29+
loading = false;
30+
}
31+
}
32+
33+
async function submitIssue() {
34+
submitError = null;
35+
if (!title) {
36+
submitError = 'Title is required';
37+
return;
38+
}
39+
submitting = true;
40+
41+
try {
42+
const res = await fetch(`https://api.github.com/repos/${OWNER}/${REPO}/issues`, {
43+
method: 'POST',
44+
headers: {
45+
Authorization: `Bearer ${GITHUB_TOKEN}`,
46+
'Content-Type': 'application/json'
47+
},
48+
body: JSON.stringify({
49+
title,
50+
body: description
51+
})
52+
});
53+
54+
if (!res.ok) {
55+
const errData = await res.json();
56+
throw new Error(errData.message || `GitHub API error: ${res.status}`);
57+
}
58+
59+
const newIssue = await res.json();
60+
issues = [newIssue, ...issues];
61+
title = '';
62+
description = '';
63+
} catch (e) {
64+
submitError = e.message;
65+
} finally {
66+
submitting = false;
67+
}
68+
}
69+
70+
onMount(fetchIssues);
71+
</script>
72+
73+
<section class="w-full max-w-5xl mx-auto px-6 pt-4 pb-16 space-y-8">
74+
<h1 class="text-2xl font-light tracking-tight">Contribute a topic — I'm gathering things to dig into</h1>
75+
76+
<form on:submit|preventDefault={submitIssue} class="space-y-4">
77+
<div>
78+
<label for="title" class="block text-sm font-medium text-gray-700">Title</label>
79+
<input
80+
id="title"
81+
type="text"
82+
bind:value={title}
83+
required
84+
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md shadow-sm bg-white focus:outline-none focus:ring focus:border-blue-300"
85+
/>
86+
87+
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
88+
<textarea
89+
id="description"
90+
rows="4"
91+
bind:value={description}
92+
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md shadow-sm bg-white focus:outline-none focus:ring focus:border-blue-300"
93+
></textarea>
94+
</div>
95+
96+
<div>
97+
<button
98+
type="submit"
99+
disabled={submitting}
100+
class="border border-black text-black bg-transparent px-4 py-2 rounded-md transition font-normal hover:font-semibold disabled:opacity-50"
101+
>
102+
{submitting ? 'Submitting...' : 'Submit'}
103+
</button>
104+
{#if submitError}
105+
<p class="text-red-600 mt-2">{submitError}</p>
106+
{/if}
107+
</div>
108+
</form>
109+
110+
<hr class="border-t border-gray-300 mt-10" />
111+
112+
<div class="mt-10 space-y-4">
113+
{#if loading}
114+
<p class="text-gray-600">Loading issues…</p>
115+
{:else if error}
116+
<p class="text-red-600">Error: {error}</p>
117+
{:else if issues.length === 0}
118+
<p class="text-gray-600">No issues found.</p>
119+
{:else}
120+
<ul class="space-y-4">
121+
{#each issues as d}
122+
<li>
123+
<a
124+
href={d.html_url}
125+
target="_blank"
126+
rel="noopener noreferrer"
127+
class="text-black hover:underline text-lg"
128+
>
129+
{d.title}
130+
</a>
131+
</li>
132+
{/each}
133+
</ul>
134+
{/if}
135+
</div>
136+
</section>

0 commit comments

Comments
 (0)