mirror of
https://github.com/logos-co/devrel-station.git
synced 2026-08-27 19:31:11 +00:00
Milestone cards show tracking data only (status, payout, dates, reviewer, deliverables, links). Review discussion happens on the proposal issue on GitHub; the app no longer fetches comments or posts reviews — drops ReviewComposer, postReviewComment, fetchMilestoneReviews, and the MilestoneReview comment-parsing conventions. The PAT is now only needed for filing feedback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
// Client-side GitHub access. The token lives only in the engineer's browser
|
|
// (localStorage) and is sent only to api.github.com — the dashboard itself
|
|
// stays a static site with no server-held secrets.
|
|
import { FEEDBACK_LABEL, FEEDBACK_REPO } from "./config";
|
|
import type { FeedbackIssue } from "./types";
|
|
|
|
const TOKEN_KEY = "rfp-tracker:github-token";
|
|
export const TOKEN_EVENT = "rfp-tracker:token-changed";
|
|
|
|
export function getStoredToken(): string | null {
|
|
if (typeof window === "undefined") return null;
|
|
return window.localStorage.getItem(TOKEN_KEY);
|
|
}
|
|
|
|
export function storeToken(token: string | null) {
|
|
if (token) window.localStorage.setItem(TOKEN_KEY, token);
|
|
else window.localStorage.removeItem(TOKEN_KEY);
|
|
window.dispatchEvent(new Event(TOKEN_EVENT));
|
|
}
|
|
|
|
async function gh(path: string, init: RequestInit = {}) {
|
|
const token = getStoredToken();
|
|
if (!token) throw new Error("No GitHub token connected");
|
|
const res = await fetch(`https://api.github.com${path}`, {
|
|
...init,
|
|
headers: {
|
|
Accept: "application/vnd.github+json",
|
|
Authorization: `Bearer ${token}`,
|
|
...(init.body ? { "Content-Type": "application/json" } : {}),
|
|
},
|
|
});
|
|
if (!res.ok) {
|
|
let detail = "";
|
|
try {
|
|
detail = ((await res.json()) as { message?: string }).message ?? "";
|
|
} catch {
|
|
/* non-JSON error body */
|
|
}
|
|
const hint =
|
|
res.status === 401
|
|
? "Token is invalid or expired"
|
|
: res.status === 403 || res.status === 404
|
|
? "Token lacks permission on this repository"
|
|
: `GitHub returned ${res.status}`;
|
|
throw new Error(detail ? `${hint} — ${detail}` : hint);
|
|
}
|
|
return res.status === 204 ? null : res.json();
|
|
}
|
|
|
|
export async function fetchViewer(): Promise<{ login: string }> {
|
|
return gh("/user") as Promise<{ login: string }>;
|
|
}
|
|
|
|
// Files community feedback as a `feedback`-labeled issue in FEEDBACK_REPO
|
|
// under the connected user's account. Labels only stick when the token's
|
|
// user has triage rights on the repo.
|
|
export async function createFeedbackIssue(
|
|
title: string,
|
|
body: string,
|
|
): Promise<FeedbackIssue> {
|
|
const it = (await gh(`/repos/${FEEDBACK_REPO}/issues`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ title, body, labels: [FEEDBACK_LABEL] }),
|
|
})) as {
|
|
number: number;
|
|
title: string;
|
|
body?: string | null;
|
|
html_url: string;
|
|
state: string;
|
|
created_at: string;
|
|
comments: number;
|
|
user: { login: string };
|
|
labels: { name: string }[];
|
|
};
|
|
return {
|
|
repo: FEEDBACK_REPO,
|
|
number: it.number,
|
|
title: it.title,
|
|
body: it.body ?? "",
|
|
url: it.html_url,
|
|
state: it.state === "closed" ? "closed" : "open",
|
|
author: it.user.login,
|
|
created_at: it.created_at,
|
|
labels: it.labels.map((l) => l.name).filter((n) => n !== FEEDBACK_LABEL),
|
|
comments: it.comments,
|
|
};
|
|
}
|
|
|
|
|