fix(workshop): simplify GitHub identities

This commit is contained in:
Thomas Camlong committed 2026-08-10 15:14:32 +02:00
1 parent 015e0cbf63
commit 64403079d8
19 files changed
+114 -362

No files matched your search

@@ -3,6 +3,7 @@ import { IconCheck, IconEdit, IconMessageCircle, IconRefresh, IconSend, IconTras
import type { WorkshopComment } from "@site/src/lib/pocketbase";
import { getWorkshopBackend } from "@site/src/lib/pocketbase";
import { githubAvatarUrl, githubProfileUrl } from "@homarr/workshop/schema";
import type { WorkshopUser } from "@homarr/workshop/schema";
import { errorMessage } from "@site/src/lib/utils";
@@ -158,13 +159,12 @@ export const CommentsSection = ({ submissionId, backend, currentUser, onRequireA
const canDelete = ownComment || currentUser?.isAdmin === true;
const isEditing = editingId === comment.id;
const edited = comment.updated !== comment.created;
const authorName = ownComment ? (currentUser?.displayName ?? comment.authorName) : comment.authorName;
const avatarUrl = ownComment
? (currentUser?.avatarUrl ?? comment.authorAvatarUrl)
: comment.authorAvatarUrl;
const profileUrl = ownComment
? (currentUser?.githubProfileUrl ?? comment.authorGithubProfileUrl)
: comment.authorGithubProfileUrl;
const authorGithubUsername = ownComment
? (currentUser?.githubUsername ?? comment.authorGithubUsername)
: comment.authorGithubUsername;
const authorName = authorGithubUsername || "Community member";
const avatarUrl = githubAvatarUrl(authorGithubUsername);
const profileUrl = githubProfileUrl(authorGithubUsername);
return (
<article
@@ -292,9 +292,9 @@ export const CommentsSection = ({ submissionId, backend, currentUser, onRequireA
<div className="rounded-xl border border-border bg-muted/30 p-3">
<div className="flex items-start gap-2.5">
<Avatar className="mt-0.5 size-9 bg-background ring-1 ring-border">
{currentUser?.avatarUrl && <AvatarImage src={currentUser.avatarUrl} alt="" />}
{currentUser?.githubUsername && <AvatarImage src={githubAvatarUrl(currentUser.githubUsername)} alt="" />}
<AvatarFallback className="bg-background">
{avatarFallback(currentUser?.displayName ?? "Guest")}
{avatarFallback(currentUser?.githubUsername ?? "Guest")}
</AvatarFallback>
</Avatar>
<Textarea
@@ -33,6 +33,8 @@ import { getRuntimeWorkshopApiUrl } from "@site/src/lib/runtime-config";
import type { SubmissionType } from "@site/src/lib/workshop-schema";
import type { HomarrCustomWidgetV2 } from "@homarr/custom-widgets/core";
import {
githubAvatarUrl,
githubProfileUrl,
MAX_WORKSHOP_SCREENSHOTS,
MAX_WORKSHOP_SCREENSHOT_BYTES,
WORKSHOP_SCREENSHOT_MIME_TYPES,
@@ -524,10 +526,10 @@ const MarketplaceDetail = ({ workshopUrl }: { workshopUrl: string }) => {
const canManage = user?.id === submission.author || user?.isAdmin === true;
const canEdit = user?.id === submission.author;
const signedInAuthor = user?.id === submission.author ? user : null;
const authorName = signedInAuthor?.displayName || submission.authorName;
const authorAvatarUrl = signedInAuthor?.avatarUrl || submission.authorAvatarUrl;
const authorGithubUsername = signedInAuthor?.githubUsername || submission.authorGithubUsername;
const authorGithubProfileUrl = signedInAuthor?.githubProfileUrl || submission.authorGithubProfileUrl;
const authorName = authorGithubUsername || "Community member";
const authorAvatarUrl = githubAvatarUrl(authorGithubUsername);
const authorGithubProfileUrl = githubProfileUrl(authorGithubUsername);
const sources = widgetDefinition ? Object.entries(widgetDefinition.sources) : [];
const requests = widgetDefinition ? Object.entries(widgetDefinition.requests) : [];
const options = widgetDefinition ? Object.entries(widgetDefinition.options) : [];
@@ -593,10 +595,7 @@ const MarketplaceDetail = ({ workshopUrl }: { workshopUrl: string }) => {
{authorName}
{authorGithubProfileUrl && <IconBrandGithub size={14} />}
</a>
<p className="text-xs text-muted-foreground">
{authorGithubUsername ? `@${authorGithubUsername} · ` : ""}
Published {formatRelativeTime(submission.created)}
</p>
<p className="text-xs text-muted-foreground">Published {formatRelativeTime(submission.created)}</p>
</div>
</div>
</div>
@@ -1,6 +1,7 @@
import React, { useEffect, useRef, useState } from "react";
import { IconBrandGithub, IconChevronDown, IconExternalLink, IconLogout } from "@tabler/icons-react";
import { githubAvatarUrl, githubProfileUrl } from "@homarr/workshop/schema";
import type { WorkshopUser } from "@homarr/workshop/schema";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -9,10 +10,6 @@ import { cn } from "@/lib/utils";
const avatarFallback = (name: string) => name.trim().slice(0, 1).toUpperCase() || "?";
const githubProfileUrl = (user: WorkshopUser) =>
user.githubProfileUrl ||
(user.githubUsername ? `https://github.com/${encodeURIComponent(user.githubUsername)}` : undefined);
interface WorkshopAccountMenuProps {
user: WorkshopUser;
onSignOut: () => void;
@@ -21,8 +18,9 @@ interface WorkshopAccountMenuProps {
export const WorkshopAccountMenu = ({ user, onSignOut }: WorkshopAccountMenuProps) => {
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
const profileUrl = githubProfileUrl(user);
const accountLabel = user.githubUsername ? `@${user.githubUsername}` : user.displayName || "Account";
const profileUrl = githubProfileUrl(user.githubUsername);
const avatarUrl = githubAvatarUrl(user.githubUsername);
const accountLabel = user.githubUsername ? `@${user.githubUsername}` : "Account";
useEffect(() => {
if (!open) return;
@@ -54,8 +52,8 @@ export const WorkshopAccountMenu = ({ user, onSignOut }: WorkshopAccountMenuProp
onClick={() => setOpen((value) => !value)}
>
<Avatar className="size-6">
{user.avatarUrl && <AvatarImage src={user.avatarUrl} alt="" />}
<AvatarFallback className="text-[10px]">{avatarFallback(user.displayName)}</AvatarFallback>
{avatarUrl && <AvatarImage src={avatarUrl} alt="" />}
<AvatarFallback className="text-[10px]">{avatarFallback(user.githubUsername)}</AvatarFallback>
</Avatar>
<span className="max-w-36 truncate">{accountLabel}</span>
<IconChevronDown
@@ -73,12 +71,11 @@ export const WorkshopAccountMenu = ({ user, onSignOut }: WorkshopAccountMenuProp
>
<div className="flex items-center gap-3 px-2 py-2">
<Avatar className="size-9">
{user.avatarUrl && <AvatarImage src={user.avatarUrl} alt="" />}
<AvatarFallback>{avatarFallback(user.displayName)}</AvatarFallback>
{avatarUrl && <AvatarImage src={avatarUrl} alt="" />}
<AvatarFallback>{avatarFallback(user.githubUsername)}</AvatarFallback>
</Avatar>
<div className="min-w-0">
<p className="truncate text-sm font-medium">{user.displayName}</p>
{user.githubUsername && <p className="truncate text-xs text-muted-foreground">@{user.githubUsername}</p>}
<p className="truncate text-sm font-medium">{accountLabel}</p>
</div>
</div>
@@ -10,6 +10,7 @@ import {
} from "@tabler/icons-react";
import { WorkshopBackend } from "@homarr/workshop/backend";
import { githubAvatarUrl, githubProfileUrl } from "@homarr/workshop/schema";
import type { WorkshopReport, WorkshopSubmissionSummary, WorkshopUser } from "@homarr/workshop/schema";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
@@ -194,18 +195,20 @@ export function WorkshopAdmin({ workshopUrl }: { workshopUrl?: string }) {
<CardHeader className="border-b border-border pb-4 sm:grid-cols-[1fr_auto]">
<div className="flex min-w-0 items-center gap-3">
<Avatar className="size-10">
{submission.authorAvatarUrl && <AvatarImage src={submission.authorAvatarUrl} alt="" />}
<AvatarFallback>{avatarFallback(submission.authorName)}</AvatarFallback>
{submission.authorGithubUsername && (
<AvatarImage src={githubAvatarUrl(submission.authorGithubUsername)} alt="" />
)}
<AvatarFallback>{avatarFallback(submission.authorGithubUsername)}</AvatarFallback>
</Avatar>
<div className="min-w-0">
<CardTitle className="truncate text-base">{submission.title}</CardTitle>
<a
href={submission.authorGithubProfileUrl || undefined}
target={submission.authorGithubProfileUrl ? "_blank" : undefined}
href={githubProfileUrl(submission.authorGithubUsername) || undefined}
target={submission.authorGithubUsername ? "_blank" : undefined}
rel="noreferrer"
className="text-xs text-muted-foreground hover:text-foreground hover:underline"
>
{submission.authorName}
{submission.authorGithubUsername || "Community member"}
</a>
</div>
</div>
@@ -237,7 +240,8 @@ export function WorkshopAdmin({ workshopUrl }: { workshopUrl?: string }) {
<div className="min-w-0">
<AlertTitle className="col-auto capitalize">{report.category} report</AlertTitle>
<p className="m-0 mt-1 text-xs text-destructive/75">
Reported by {report.reporterName} · {new Date(report.created).toLocaleString()}
Reported by {report.reporterGithubUsername || "Community member"} ·{" "}
{new Date(report.created).toLocaleString()}
</p>
</div>
<Button
@@ -18,6 +18,7 @@ import {
import type { WorkshopSubmission } from "@site/src/lib/pocketbase";
import type { SubmissionType } from "@site/src/lib/workshop-schema";
import { githubAvatarUrl, githubProfileUrl } from "@homarr/workshop/schema";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -98,7 +99,8 @@ export const WorkshopApp = ({ workshopUrl }: { workshopUrl: string }) => {
.filter((item) => includeOutdated || !item.outdated)
.filter(
(item) =>
!q || [item.title, item.description, item.authorName].some((value) => value?.toLowerCase().includes(q)),
!q ||
[item.title, item.description, item.authorGithubUsername].some((value) => value?.toLowerCase().includes(q)),
)
.toSorted(workshop.sorters[sort])
.map((s) => s.id);
@@ -356,16 +358,18 @@ const SubmissionCard = ({ submission, backend, userVote, onVote }: SubmissionCar
</div>
<CardDescription className="flex min-w-0 items-center gap-1 text-xs">
<a
href={submission.authorGithubProfileUrl || undefined}
target={submission.authorGithubProfileUrl ? "_blank" : undefined}
href={githubProfileUrl(submission.authorGithubUsername) || undefined}
target={submission.authorGithubUsername ? "_blank" : undefined}
rel="noreferrer"
className="inline-flex min-w-0 items-center gap-1.5 hover:text-foreground"
>
<Avatar className="size-4">
{submission.authorAvatarUrl && <AvatarImage src={submission.authorAvatarUrl} alt="" />}
<AvatarFallback className="text-[9px]">{avatarFallback(submission.authorName)}</AvatarFallback>
{submission.authorGithubUsername && (
<AvatarImage src={githubAvatarUrl(submission.authorGithubUsername)} alt="" />
)}
<AvatarFallback className="text-[9px]">{avatarFallback(submission.authorGithubUsername)}</AvatarFallback>
</Avatar>
<span className="truncate">{submission.authorName}</span>
<span className="truncate">{submission.authorGithubUsername || "Community member"}</span>
</a>
<span className="shrink-0 whitespace-nowrap">
· v{submission.revision} · {formatRelativeTime(submission.created)}