From e4022c7bc216ce810fd6b72e2c41d3116a207349 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Sun, 1 Feb 2026 14:31:14 +0100 Subject: [PATCH 01/13] add isActive and classList to Button, move H2 and H3 to Headers --- frontend/__tests__/components/Button.spec.tsx | 38 +++++++++++++++++++ frontend/src/ts/components/common/Button.tsx | 11 +++++- frontend/src/ts/components/common/Headers.tsx | 21 ++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 frontend/src/ts/components/common/Headers.tsx diff --git a/frontend/__tests__/components/Button.spec.tsx b/frontend/__tests__/components/Button.spec.tsx index 4c0ca6db2746..0a6912d04931 100644 --- a/frontend/__tests__/components/Button.spec.tsx +++ b/frontend/__tests__/components/Button.spec.tsx @@ -174,4 +174,42 @@ describe("Button component", () => { expect(child).toBeTruthy(); expect(child?.textContent).toBe("Child"); }); + + it("applies custom class list when classList prop is provided", () => { + const { container } = render(() => ( + From baf35bae88ec599831531ff1734b215b98f526bc Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Sun, 1 Feb 2026 14:59:11 +0100 Subject: [PATCH 03/13] move tests to correct folders --- .../__tests__/components/{ => common}/AnimatedModal.spec.tsx | 2 +- frontend/__tests__/components/{ => common}/Button.spec.tsx | 2 +- .../components/{ => layout/footer}/ScrollToTop.spec.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename frontend/__tests__/components/{ => common}/AnimatedModal.spec.tsx (96%) rename frontend/__tests__/components/{ => common}/Button.spec.tsx (99%) rename frontend/__tests__/components/{ => layout/footer}/ScrollToTop.spec.tsx (95%) diff --git a/frontend/__tests__/components/AnimatedModal.spec.tsx b/frontend/__tests__/components/common/AnimatedModal.spec.tsx similarity index 96% rename from frontend/__tests__/components/AnimatedModal.spec.tsx rename to frontend/__tests__/components/common/AnimatedModal.spec.tsx index 354cdf0f997c..b57934428ace 100644 --- a/frontend/__tests__/components/AnimatedModal.spec.tsx +++ b/frontend/__tests__/components/common/AnimatedModal.spec.tsx @@ -1,7 +1,7 @@ import { render } from "@solidjs/testing-library"; import { describe, it, expect, vi, beforeEach } from "vitest"; -import { AnimatedModal } from "../../src/ts/components/common/AnimatedModal"; +import { AnimatedModal } from "../../../src/ts/components/common/AnimatedModal"; describe("AnimatedModal", () => { beforeEach(() => { diff --git a/frontend/__tests__/components/Button.spec.tsx b/frontend/__tests__/components/common/Button.spec.tsx similarity index 99% rename from frontend/__tests__/components/Button.spec.tsx rename to frontend/__tests__/components/common/Button.spec.tsx index 958d476d4c69..7f073576e586 100644 --- a/frontend/__tests__/components/Button.spec.tsx +++ b/frontend/__tests__/components/common/Button.spec.tsx @@ -1,7 +1,7 @@ import { cleanup, render } from "@solidjs/testing-library"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { Button } from "../../src/ts/components/common/Button"; +import { Button } from "../../../src/ts/components/common/Button"; describe("Button component", () => { afterEach(() => { diff --git a/frontend/__tests__/components/ScrollToTop.spec.tsx b/frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx similarity index 95% rename from frontend/__tests__/components/ScrollToTop.spec.tsx rename to frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx index 36021bfcc7ae..99b74cb4373d 100644 --- a/frontend/__tests__/components/ScrollToTop.spec.tsx +++ b/frontend/__tests__/components/layout/footer/ScrollToTop.spec.tsx @@ -2,8 +2,8 @@ import { render } from "@solidjs/testing-library"; import { userEvent } from "@testing-library/user-event"; import { describe, it, expect, vi, beforeEach } from "vitest"; -import { ScrollToTop } from "../../src/ts/components/layout/footer/ScrollToTop"; -import * as CoreSignals from "../../src/ts/signals/core"; +import { ScrollToTop } from "../../../../src/ts/components/layout/footer/ScrollToTop"; +import * as CoreSignals from "../../../../src/ts/signals/core"; describe("ScrollToTop", () => { const getActivePageMock = vi.spyOn(CoreSignals, "getActivePage"); From 1248b81dd9180093fd39de61fdc986458158e4cf Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Sun, 1 Feb 2026 15:06:18 +0100 Subject: [PATCH 04/13] add LoadingCircle --- frontend/src/ts/components/common/AsyncContent.tsx | 8 ++------ frontend/src/ts/components/common/Fa.tsx | 4 +++- frontend/src/ts/components/common/LoadingCircle.tsx | 10 ++++++++++ 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 frontend/src/ts/components/common/LoadingCircle.tsx diff --git a/frontend/src/ts/components/common/AsyncContent.tsx b/frontend/src/ts/components/common/AsyncContent.tsx index be3e4a37037f..d8d504fec9d0 100644 --- a/frontend/src/ts/components/common/AsyncContent.tsx +++ b/frontend/src/ts/components/common/AsyncContent.tsx @@ -13,7 +13,7 @@ import * as Notifications from "../../elements/notifications"; import { createErrorMessage } from "../../utils/misc"; import { Conditional } from "./Conditional"; -import { Fa } from "./Fa"; +import { LoadingCircle } from "./LoadingCircle"; export default function AsyncContent( props: { @@ -78,11 +78,7 @@ export default function AsyncContent( return message; }; - const loader = ( -
- -
- ); + const loader = ; const errorText = (err: unknown): JSXElement => (
{handleError(err)}
diff --git a/frontend/src/ts/components/common/Fa.tsx b/frontend/src/ts/components/common/Fa.tsx index 60604d84f930..ae8d20b92543 100644 --- a/frontend/src/ts/components/common/Fa.tsx +++ b/frontend/src/ts/components/common/Fa.tsx @@ -1,18 +1,20 @@ import { JSXElement } from "solid-js"; import { FaObject } from "../../types/font-awesome"; +import { cn } from "../../utils/cn"; export type FaProps = { fixedWidth?: boolean; spin?: boolean; size?: number; + class?: string; } & FaObject; export function Fa(props: FaProps): JSXElement { const variant = (): string => props.variant ?? "solid"; return ( + + + ); +} From f6c57679977101e4efddc513714a5f5f9d8e8133 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Sun, 1 Feb 2026 15:35:40 +0100 Subject: [PATCH 05/13] add DiscordAvatar and User --- .../ts/components/common/DiscordAvatar.tsx | 58 ++++++++++ frontend/src/ts/components/common/User.tsx | 102 ++++++++++++++++++ .../src/ts/components/pages/AboutPage.tsx | 1 + .../src/ts/controllers/badge-controller.ts | 65 +++++++---- .../ts/controllers/user-flag-controller.ts | 14 ++- 5 files changed, 213 insertions(+), 27 deletions(-) create mode 100644 frontend/src/ts/components/common/DiscordAvatar.tsx create mode 100644 frontend/src/ts/components/common/User.tsx diff --git a/frontend/src/ts/components/common/DiscordAvatar.tsx b/frontend/src/ts/components/common/DiscordAvatar.tsx new file mode 100644 index 000000000000..82024c34b40f --- /dev/null +++ b/frontend/src/ts/components/common/DiscordAvatar.tsx @@ -0,0 +1,58 @@ +import { JSXElement, Show } from "solid-js"; +import { createStore } from "solid-js/store"; + +import { FaSolidIcon } from "../../types/font-awesome"; + +import { Conditional } from "./Conditional"; +import { Fa } from "./Fa"; + +//cache successful and missing avatars +const [avatar, setAvatar] = createStore>(); + +export function DiscordAvatar(props: { + discordId: string | undefined; + discordAvatar: string | undefined; + size?: number; + missingIcon?: FaSolidIcon; +}): JSXElement { + return ( +
+
+ + + + + { + setAvatar(`${props.discordId}/${props.discordAvatar}`, true); + }} + onError={() => { + setAvatar(`${props.discordId}/${props.discordAvatar}`, false); + }} + /> + + } + else={} + /> +
+
+ ); +} diff --git a/frontend/src/ts/components/common/User.tsx b/frontend/src/ts/components/common/User.tsx new file mode 100644 index 000000000000..2da5f9802c90 --- /dev/null +++ b/frontend/src/ts/components/common/User.tsx @@ -0,0 +1,102 @@ +import { User as UserType } from "@monkeytype/schemas/users"; +import { For, JSXElement, Show } from "solid-js"; + +import { + badges, + UserBadge as UserBadgeType, +} from "../../controllers/badge-controller"; +import { + getMatchingFlags, + SupportsFlags, + UserFlag, + UserFlagOptions, +} from "../../controllers/user-flag-controller"; + +import { Button } from "./Button"; +import { DiscordAvatar } from "./DiscordAvatar"; +import { Fa } from "./Fa"; + +export function User( + props: { + user: SupportsFlags & + Pick & { + badgeId?: number; + }; + showAvatar?: boolean; + } & UserFlagOptions, +): JSXElement { + return ( +
+ + + + +
+ ); +} + +function UserFlags(props: SupportsFlags & UserFlagOptions): JSXElement { + const flags = (): UserFlag[] => getMatchingFlags(props); + console.log("####", props, flags()); + + return ( + + {(flag) => ( + }> +
+ {} +
+
+ )} +
+ ); +} + +function UserBadge(props: { id?: number }): JSXElement { + const badge = (): UserBadgeType | undefined => + props.id !== undefined ? badges[props.id] : undefined; + return ( + +
+ + + + +
+
+ ); +} diff --git a/frontend/src/ts/components/pages/AboutPage.tsx b/frontend/src/ts/components/pages/AboutPage.tsx index 20335d005d87..081cf059c51b 100644 --- a/frontend/src/ts/components/pages/AboutPage.tsx +++ b/frontend/src/ts/components/pages/AboutPage.tsx @@ -16,6 +16,7 @@ import AsyncContent from "../common/AsyncContent"; import { Button } from "../common/Button"; import { ChartJs } from "../common/ChartJs"; import { Fa, FaProps } from "../common/Fa"; +import { User } from "../common/User"; function H2(props: { text: string; fa: FaProps }): JSXElement { return ( diff --git a/frontend/src/ts/controllers/badge-controller.ts b/frontend/src/ts/controllers/badge-controller.ts index 2532ab4ed4d8..5b556a868c12 100644 --- a/frontend/src/ts/controllers/badge-controller.ts +++ b/frontend/src/ts/controllers/badge-controller.ts @@ -1,22 +1,28 @@ -type UserBadge = { +import { JSX } from "solid-js/jsx-runtime"; +import { FaSolidIcon } from "../types/font-awesome"; + +export type UserBadge = { id: number; name: string; description: string; - icon?: string; + icon?: FaSolidIcon; background?: string; color?: string; - customStyle?: string; + customStyle?: JSX.CSSProperties; }; -const badges: Record = { +export const badges: Record = { 1: { id: 1, name: "Developer", description: "I made this", icon: "fa-laptop", color: "white", - customStyle: - "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + customStyle: { + animation: "rgb-bg 10s linear infinite", + background: + "linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%)", + }, }, 2: { id: 2, @@ -24,8 +30,11 @@ const badges: Record = { description: "I helped make this", icon: "fa-code", color: "white", - customStyle: - "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + customStyle: { + animation: "rgb-bg 10s linear infinite", + background: + "linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%)", + }, }, 3: { id: 3, @@ -33,8 +42,11 @@ const badges: Record = { description: "Discord server moderator", icon: "fa-hammer", color: "white", - customStyle: - "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + customStyle: { + animation: "rgb-bg 10s linear infinite", + background: + "linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + }, }, 4: { id: 4, @@ -114,8 +126,11 @@ const badges: Record = { description: "Yes, I'm actually this fast", icon: "fa-rocket", color: "white", - customStyle: - "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + customStyle: { + animation: "rgb-bg 10s linear infinite", + background: + "linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + }, }, 14: { id: 14, @@ -132,8 +147,11 @@ const badges: Record = { icon: "fa-bomb", color: "white", background: "#093d79", - customStyle: - "animation: gold-shimmer 10s cubic-bezier(0.5, 0, 0.5, 1) infinite; background: linear-gradient(90deg, rgb(8 31 84) 0%, rgb(18 134 158) 100%); background-size: 200% 200%;", + customStyle: { + animation: "gold-shimmer 10s cubic-bezier(0.5, 0, 0.5, 1) infinite", + background: + "linear-gradient(90deg, rgb(8 31 84) 0%, rgb(18 134 158) 100%); background-size: 200% 200%;", + }, }, 16: { id: 16, @@ -141,8 +159,12 @@ const badges: Record = { description: "Longest test with zero mistakes - 4 hours and 1 minute", icon: "fa-bullseye", color: "white", - customStyle: - "animation: gold-shimmer 10s cubic-bezier(0.5, -0.15, 0.5, 1.15) infinite; background: linear-gradient(45deg, #b8860b 0%, #daa520 25%, #ffd700 50%, #daa520 75%, #b8860b 100%); background-size: 200% 200%;", + customStyle: { + animation: + "gold-shimmer 10s cubic-bezier(0.5, -0.15, 0.5, 1.15) infinite", + background: + "linear-gradient(45deg, #b8860b 0%, #daa520 25%, #ffd700 50%, #daa520 75%, #b8860b 100%); background-size: 200% 200%;", + }, }, 17: { id: 17, @@ -150,8 +172,11 @@ const badges: Record = { description: "Ferb, I know what we're gonna do today...", icon: "fa-sun", color: "white", - customStyle: - "animation: rgb-bg 10s linear infinite; background: linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + customStyle: { + animation: "rgb-bg 10s linear infinite", + background: + "linear-gradient(45deg in hsl longer hue, hsl(330, 90%, 30%) 0%, hsl(250, 90%, 30%) 100%);", + }, }, }; @@ -175,7 +200,9 @@ export function getHTMLById( style += `color: ${badge.color};`; } if (badge?.customStyle !== undefined) { - style += badge.customStyle; + style += Object.entries(badge.customStyle) + .map(([key, value]) => `${key}: ${value};`) + .join(";"); } const badgeName = badge?.name ?? "Badge Name Missing"; diff --git a/frontend/src/ts/controllers/user-flag-controller.ts b/frontend/src/ts/controllers/user-flag-controller.ts index 790f83dcd8bc..58d588eb2c64 100644 --- a/frontend/src/ts/controllers/user-flag-controller.ts +++ b/frontend/src/ts/controllers/user-flag-controller.ts @@ -1,3 +1,5 @@ +import { FaSolidIcon } from "../types/font-awesome"; + const flags: UserFlag[] = [ { name: "Prime Ape", @@ -34,17 +36,16 @@ export type SupportsFlags = { isFriend?: boolean; }; -type UserFlag = { +export type UserFlag = { readonly name: string; readonly description: string; - readonly icon: string; + readonly icon: FaSolidIcon; readonly color?: string; readonly background?: string; - readonly customStyle?: string; test(source: SupportsFlags): boolean; }; -type UserFlagOptions = { +export type UserFlagOptions = { iconsOnly?: boolean; isFriend?: boolean; }; @@ -53,7 +54,7 @@ const USER_FLAG_OPTIONS_DEFAULT: UserFlagOptions = { iconsOnly: false, }; -function getMatchingFlags(source: SupportsFlags): UserFlag[] { +export function getMatchingFlags(source: SupportsFlags): UserFlag[] { const result = flags.filter((it) => it.test(source)); return result; } @@ -71,9 +72,6 @@ function toHtml(flag: UserFlag, formatOptions: UserFlagOptions): string { if (flag?.color !== undefined) { style.push(`color: ${flag.color};`); } - if (flag?.customStyle !== undefined) { - style.push(flag.customStyle); - } const balloon = `aria-label="${flag.description}" data-balloon-pos="right"`; From 7b7a5e46d3920484d5caf78a79f736b27604a5d5 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Tue, 3 Feb 2026 16:26:47 +0100 Subject: [PATCH 06/13] button, headers --- .../components/common/Button.spec.tsx | 19 ++++++++++++++++- frontend/src/ts/components/common/Button.tsx | 3 +++ frontend/src/ts/components/common/Headers.tsx | 21 +++++++++++++++---- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/frontend/__tests__/components/common/Button.spec.tsx b/frontend/__tests__/components/common/Button.spec.tsx index 7f073576e586..0488c8b81d9a 100644 --- a/frontend/__tests__/components/common/Button.spec.tsx +++ b/frontend/__tests__/components/common/Button.spec.tsx @@ -18,6 +18,7 @@ describe("Button component", () => { const button = container.querySelector("button"); expect(button).toBeTruthy(); expect(button).toHaveTextContent("Click me"); + expect(button).not.toBeDisabled(); }); it("renders an anchor element when href is provided", () => { @@ -269,7 +270,8 @@ describe("Button component", () => { expect(anchor).toHaveAttribute("aria-label", "test"); expect(anchor).toHaveAttribute("data-balloon-pos", "down"); }); - it("applies router-link to ancor", () => { + + it("applies router-link to anchor", () => { const { container } = render(() => ( diff --git a/frontend/src/ts/components/common/Headers.tsx b/frontend/src/ts/components/common/Headers.tsx index f7f2fcfb2e77..47d00ee34819 100644 --- a/frontend/src/ts/components/common/Headers.tsx +++ b/frontend/src/ts/components/common/Headers.tsx @@ -1,11 +1,24 @@ -import { JSXElement } from "solid-js"; +import { JSXElement, Show } from "solid-js"; import { Fa, FaProps } from "./Fa"; -export function H2(props: { text: string; fa: FaProps }): JSXElement { +export function H1(props: { text: string; fa?: FaProps }): JSXElement { return ( -

- +

+ + + + {props.text} +

+ ); +} + +export function H2(props: { text: string; fa?: FaProps }): JSXElement { + return ( +

+ + + {props.text}

); From 2a03d7c3696d1748388f52907a707977379cd9a3 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Tue, 3 Feb 2026 16:37:16 +0100 Subject: [PATCH 07/13] add maxBreakpoint and alignment to table, rename sortableHeaderMeta to headerMeta --- .../src/ts/components/ui/table/DataTable.tsx | 187 +++++++++++------- frontend/src/ts/components/ui/table/Table.tsx | 2 +- frontend/src/ts/types/tanstack-table.d.ts | 15 +- 3 files changed, 129 insertions(+), 75 deletions(-) diff --git a/frontend/src/ts/components/ui/table/DataTable.tsx b/frontend/src/ts/components/ui/table/DataTable.tsx index 683765cf6880..7ada4789d9d5 100644 --- a/frontend/src/ts/components/ui/table/DataTable.tsx +++ b/frontend/src/ts/components/ui/table/DataTable.tsx @@ -32,16 +32,17 @@ const SortingStateSchema = z.array( }), ); -export type AnyColumnDef = +export type DataTableColumnDef = | ColumnDef | AccessorFnColumnDef | AccessorKeyColumnDef; type DataTableProps = { id: string; - columns: AnyColumnDef[]; + columns: DataTableColumnDef[]; data: TData[]; fallback?: JSXElement; + hideHeader?: true; }; export function DataTable( @@ -77,7 +78,11 @@ export function DataTable( ? String(col.accessorKey) : `__col_${index}`); - return [id, current[col.meta?.breakpoint ?? "xxs"]]; + return [ + id, + current[col.meta?.breakpoint ?? "xxs"] && + !current[col.meta?.maxBreakpoint ?? "xxl"], + ]; }), ); @@ -107,34 +112,92 @@ export function DataTable( return ( - - - {(headerGroup) => ( - - - {(header) => ( - - + + } + else={ + {flexRender( @@ -142,47 +205,16 @@ export function DataTable( header.getContext(), )} - - }> - - - - - - - - } - else={ - - - {flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - - } - /> - )} - - - )} - - + + } + /> + )} + + + )} + + + {(row) => ( @@ -197,7 +229,18 @@ export function DataTable( }) : (cell.column.columnDef.meta?.cellMeta ?? {}); return ( - + {flexRender( cell.column.columnDef.cell, cell.getContext(), diff --git a/frontend/src/ts/components/ui/table/Table.tsx b/frontend/src/ts/components/ui/table/Table.tsx index 88f1abd7390f..e625bb19d541 100644 --- a/frontend/src/ts/components/ui/table/Table.tsx +++ b/frontend/src/ts/components/ui/table/Table.tsx @@ -57,7 +57,7 @@ const TableHead: Component> = (props) => {
JSX.HTMLAttributes); /** - * additional attributes to be set on the header if it is sortable + * additional attributes to be set on the header * Can be used to define mouse-overs with `aria-label` and `data-balloon-pos` */ - sortableHeaderMeta?: JSX.HTMLAttributes; + headerMeta?: JSX.HTMLAttributes; } } From 6857d2f29fff011b87896970fc017afb6c4d8585 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Tue, 3 Feb 2026 16:42:36 +0100 Subject: [PATCH 08/13] run format-fix --- frontend/src/ts/components/common/Headers.tsx | 6 +++--- frontend/src/ts/components/common/LoadingCircle.tsx | 2 +- frontend/src/ts/components/common/User.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/ts/components/common/Headers.tsx b/frontend/src/ts/components/common/Headers.tsx index 47d00ee34819..775430c51e8b 100644 --- a/frontend/src/ts/components/common/Headers.tsx +++ b/frontend/src/ts/components/common/Headers.tsx @@ -4,7 +4,7 @@ import { Fa, FaProps } from "./Fa"; export function H1(props: { text: string; fa?: FaProps }): JSXElement { return ( -

+

@@ -15,7 +15,7 @@ export function H1(props: { text: string; fa?: FaProps }): JSXElement { export function H2(props: { text: string; fa?: FaProps }): JSXElement { return ( -

+

@@ -26,7 +26,7 @@ export function H2(props: { text: string; fa?: FaProps }): JSXElement { export function H3(props: { text: string; fa: FaProps }): JSXElement { return ( -

+

{props.text}

diff --git a/frontend/src/ts/components/common/LoadingCircle.tsx b/frontend/src/ts/components/common/LoadingCircle.tsx index 1e1ad49098d3..180703e4a841 100644 --- a/frontend/src/ts/components/common/LoadingCircle.tsx +++ b/frontend/src/ts/components/common/LoadingCircle.tsx @@ -3,7 +3,7 @@ import { JSXElement } from "solid-js"; import { Fa } from "./Fa"; export function LoadingCircle(): JSXElement { return ( -
+
); diff --git a/frontend/src/ts/components/common/User.tsx b/frontend/src/ts/components/common/User.tsx index 2da5f9802c90..bd92b02ac9b1 100644 --- a/frontend/src/ts/components/common/User.tsx +++ b/frontend/src/ts/components/common/User.tsx @@ -41,7 +41,7 @@ export function User( router-link /> -
+
Date: Tue, 3 Feb 2026 19:06:59 +0100 Subject: [PATCH 09/13] fix visibility --- frontend/src/ts/components/ui/table/DataTable.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/ts/components/ui/table/DataTable.tsx b/frontend/src/ts/components/ui/table/DataTable.tsx index 7ada4789d9d5..49888a21d134 100644 --- a/frontend/src/ts/components/ui/table/DataTable.tsx +++ b/frontend/src/ts/components/ui/table/DataTable.tsx @@ -72,17 +72,18 @@ export function DataTable( const current = bp(); const result = Object.fromEntries( props.columns.map((col, index) => { - const id = + col.id = col.id ?? ("accessorKey" in col && col.accessorKey !== null ? String(col.accessorKey) : `__col_${index}`); - return [ - id, + const visible = current[col.meta?.breakpoint ?? "xxs"] && - !current[col.meta?.maxBreakpoint ?? "xxl"], - ]; + (col.meta?.maxBreakpoint === undefined || + !current[col.meta?.maxBreakpoint]); + + return [col.id, visible]; }), ); From 6b8c1c31eee04f4a25d28b2973a045ab6bea2bab Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Wed, 4 Feb 2026 10:47:19 +0100 Subject: [PATCH 10/13] cleanup user classes --- .../ts/components/common/DiscordAvatar.tsx | 68 ++++++++----------- frontend/src/ts/components/common/User.tsx | 9 +-- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/frontend/src/ts/components/common/DiscordAvatar.tsx b/frontend/src/ts/components/common/DiscordAvatar.tsx index 82024c34b40f..aaf1a96b91c6 100644 --- a/frontend/src/ts/components/common/DiscordAvatar.tsx +++ b/frontend/src/ts/components/common/DiscordAvatar.tsx @@ -1,9 +1,8 @@ -import { JSXElement, Show } from "solid-js"; +import { createSignal, JSXElement, Show } from "solid-js"; import { createStore } from "solid-js/store"; import { FaSolidIcon } from "../../types/font-awesome"; -import { Conditional } from "./Conditional"; import { Fa } from "./Fa"; //cache successful and missing avatars @@ -15,44 +14,35 @@ export function DiscordAvatar(props: { size?: number; missingIcon?: FaSolidIcon; }): JSXElement { + const cacheKey = (): string => `${props.discordId}/${props.discordAvatar}`; + const [showSpinner, setShowSpinner] = createSignal(true); return ( -
-
- - - - - { - setAvatar(`${props.discordId}/${props.discordAvatar}`, true); - }} - onError={() => { - setAvatar(`${props.discordId}/${props.discordAvatar}`, false); - }} - /> - - } - else={} - /> -
+
+ } + > + <> + + + + { + setAvatar(cacheKey(), true); + setShowSpinner(false); + }} + onError={() => { + setAvatar(cacheKey(), false); + }} + /> + +
); } diff --git a/frontend/src/ts/components/common/User.tsx b/frontend/src/ts/components/common/User.tsx index bd92b02ac9b1..079ca696a473 100644 --- a/frontend/src/ts/components/common/User.tsx +++ b/frontend/src/ts/components/common/User.tsx @@ -26,21 +26,19 @@ export function User( } & UserFlagOptions, ): JSXElement { return ( -
+
-