import { AlertTriangle, BookOpen, Bug, Calendar, ChevronDown, ChevronUp, Coffee, ExternalLink, Heart, History, Lightbulb, Loader2, Tag, } from 'lucide-react'; import { type ComponentType, type CSSProperties, type ReactNode } from 'react'; import { useTranslation } from '../../i18n'; import { escapeReleaseHtml as escapeHtml, parseReleaseNotes } from '../../utils/releaseNotes'; import { fs } from '../shared/DialogShell'; import { SETTINGS_BUTTON, SettingsCard, StatusPill } from '../Settings/settingsKit'; import { useGithubReleases } from './useGithubReleases'; const REPO = 'liketrek/TREK'; /** The Discord mark, sized and coloured like the lucide glyphs beside it. */ function DiscordIcon({ size = 18 }: { size?: number }) { return ( ); } interface SupportLink { href: string; /** The brand's own colour: the icon and the hover edge take it, nothing else. */ brand: string; icon: ComponentType<{ size?: number }>; title: ReactNode; hint: ReactNode; } /** One link to the project's other places: a card like the bookings', its brand only on the icon. */ function SupportCard({ link }: { link: SupportLink }) { const Icon = link.icon; return ( {link.title} {link.hint} ); } export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: boolean }) { const { t } = useTranslation(); const { releases, shownReleases: shown, loading, error, expanded, toggleExpand, hasMore, loadingMore, handleLoadMore, formatDate, } = useGithubReleases({ isPrerelease }); // Simple markdown-to-html for release notes (handles headers, bold, lists, links) const renderBody = (body: string) => { if (!body) return null; const inlineFormat = (text: string) => { return escapeHtml(text) .replace(/\*\*(.+?)\*\*/g, '$1') .replace( /`(.+?)`/g, '$1' ) .replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, url) => { const safeUrl = url.startsWith('http://') || url.startsWith('https://') ? url : '#'; return `${label}`; }); }; return parseReleaseNotes(body).map((block, idx) => { if (block.kind === 'ul') { return ( ); } if (block.kind === 'h4') { return (

{block.text}

); } if (block.kind === 'h3') { return (

{block.text}

); } return (

); }); }; const supportLinks: SupportLink[] = [ { href: 'https://ko-fi.com/mauriceboe', brand: '#ff5e5b', // theme-lint-disable: Ko-fi brand colour icon: Coffee, title: 'Ko-fi', hint: t('admin.github.support'), }, { href: 'https://buymeacoffee.com/mauriceboe', brand: '#e0b400', // theme-lint-disable: Buy Me a Coffee brand yellow, deepened so it reads on white icon: Heart, title: 'Buy Me a Coffee', hint: t('admin.github.support'), }, { href: 'https://discord.gg/NhZBDSd4qW', brand: '#5865F2', // theme-lint-disable: Discord brand colour icon: DiscordIcon, title: 'Discord', hint: 'Join the community', }, { href: 'https://github.com/liketrek/TREK/issues/new?template=bug_report.yml', brand: 'var(--danger)', icon: Bug, title: t('settings.about.reportBug'), hint: t('settings.about.reportBugHint'), }, { href: 'https://github.com/liketrek/TREK/discussions/new?category=feature-requests', brand: 'var(--warning)', icon: Lightbulb, title: t('settings.about.featureRequest'), hint: t('settings.about.featureRequestHint'), }, { href: 'https://github.com/liketrek/TREK/wiki', brand: 'var(--text-secondary)', icon: BookOpen, title: 'Wiki', hint: t('settings.about.wikiHint'), }, ]; const subtitle = t('admin.github.subtitle').replace('{repo}', REPO); let releasesCard: ReactNode; if (loading) { releasesCard = (

); } else if (error && releases.length === 0) { releasesCard = ; } else { releasesCard = ( GitHub } > {/* Timeline */}
{/* Timeline line, from the first dot's middle down to the last one's */} {shown.length > 1 && {/* A failed "Load more" keeps the timeline and stays retryable */} {error && (

{t('admin.github.error')}: {error}

)} {/* Load more */} {hasMore && (
)} ); } return (
{/* Support links: one card, the six places as tiles inside it */}
{supportLinks.map((link) => )}
{/* Loading / Error / Releases */} {releasesCard}
); }