refactor(skills): reorganize imports and improve code formatting
This commit is contained in:
@@ -50,16 +50,20 @@ export default function SkillsMarketplacePage() {
|
||||
const searchParams = useSearchParams();
|
||||
const { isSignedIn } = useAuth();
|
||||
const { isAdmin } = useOrganizationMembership(isSignedIn);
|
||||
const [selectedSkill, setSelectedSkill] = useState<MarketplaceSkillCardRead | null>(null);
|
||||
const [selectedSkill, setSelectedSkill] =
|
||||
useState<MarketplaceSkillCardRead | null>(null);
|
||||
const [gatewayInstalledById, setGatewayInstalledById] = useState<
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
const [installedGatewayNamesBySkillId, setInstalledGatewayNamesBySkillId] = useState<
|
||||
Record<string, string[]>
|
||||
>({});
|
||||
const [installedGatewayNamesBySkillId, setInstalledGatewayNamesBySkillId] =
|
||||
useState<Record<string, { id: string; name: string }[]>>({});
|
||||
const [isGatewayStatusLoading, setIsGatewayStatusLoading] = useState(false);
|
||||
const [gatewayStatusError, setGatewayStatusError] = useState<string | null>(null);
|
||||
const [installingGatewayId, setInstallingGatewayId] = useState<string | null>(null);
|
||||
const [gatewayStatusError, setGatewayStatusError] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [installingGatewayId, setInstallingGatewayId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const { sorting, onSortingChange } = useUrlSorting({
|
||||
allowedColumnIds: MARKETPLACE_SKILLS_SORTABLE_COLUMNS,
|
||||
@@ -161,25 +165,29 @@ export default function SkillsMarketplacePage() {
|
||||
const updateInstalledGatewayNames = useCallback(
|
||||
({
|
||||
skillId,
|
||||
gatewayId,
|
||||
gatewayName,
|
||||
installed,
|
||||
}: {
|
||||
skillId: string;
|
||||
gatewayId: string;
|
||||
gatewayName: string;
|
||||
installed: boolean;
|
||||
}) => {
|
||||
setInstalledGatewayNamesBySkillId((previous) => {
|
||||
const installedOn = previous[skillId] ?? [];
|
||||
if (installed) {
|
||||
if (installedOn.includes(gatewayName)) return previous;
|
||||
if (installedOn.some((gateway) => gateway.id === gatewayId)) {
|
||||
return previous;
|
||||
}
|
||||
return {
|
||||
...previous,
|
||||
[skillId]: [...installedOn, gatewayName],
|
||||
[skillId]: [...installedOn, { id: gatewayId, name: gatewayName }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...previous,
|
||||
[skillId]: installedOn.filter((name) => name !== gatewayName),
|
||||
[skillId]: installedOn.filter((gateway) => gateway.id !== gatewayId),
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -190,7 +198,12 @@ export default function SkillsMarketplacePage() {
|
||||
let cancelled = false;
|
||||
|
||||
const loadInstalledGatewaysBySkill = async () => {
|
||||
if (!isSignedIn || !isAdmin || gateways.length === 0 || skills.length === 0) {
|
||||
if (
|
||||
!isSignedIn ||
|
||||
!isAdmin ||
|
||||
gateways.length === 0 ||
|
||||
skills.length === 0
|
||||
) {
|
||||
setInstalledGatewayNamesBySkillId({});
|
||||
return;
|
||||
}
|
||||
@@ -198,9 +211,10 @@ export default function SkillsMarketplacePage() {
|
||||
try {
|
||||
const gatewaySkills = await Promise.all(
|
||||
gateways.map(async (gateway) => {
|
||||
const response = await listMarketplaceSkillsApiV1SkillsMarketplaceGet({
|
||||
gateway_id: gateway.id,
|
||||
});
|
||||
const response =
|
||||
await listMarketplaceSkillsApiV1SkillsMarketplaceGet({
|
||||
gateway_id: gateway.id,
|
||||
});
|
||||
return {
|
||||
gatewayId: gateway.id,
|
||||
gatewayName: gateway.name,
|
||||
@@ -211,16 +225,26 @@ export default function SkillsMarketplacePage() {
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
const nextInstalledGatewayNamesBySkillId: Record<string, string[]> = {};
|
||||
const nextInstalledGatewayNamesBySkillId: Record<
|
||||
string,
|
||||
{ id: string; name: string }[]
|
||||
> = {};
|
||||
for (const skill of skills) {
|
||||
nextInstalledGatewayNamesBySkillId[skill.id] = [];
|
||||
}
|
||||
|
||||
for (const { gatewayName, skills: gatewaySkillRows } of gatewaySkills) {
|
||||
for (const {
|
||||
gatewayId,
|
||||
gatewayName,
|
||||
skills: gatewaySkillRows,
|
||||
} of gatewaySkills) {
|
||||
for (const skill of gatewaySkillRows) {
|
||||
if (!skill.installed) continue;
|
||||
if (!nextInstalledGatewayNamesBySkillId[skill.id]) continue;
|
||||
nextInstalledGatewayNamesBySkillId[skill.id].push(gatewayName);
|
||||
nextInstalledGatewayNamesBySkillId[skill.id].push({
|
||||
id: gatewayId,
|
||||
name: gatewayName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,11 +274,13 @@ export default function SkillsMarketplacePage() {
|
||||
...previous,
|
||||
[variables.params.gateway_id]: true,
|
||||
}));
|
||||
const gatewayName =
|
||||
gateways.find((gateway) => gateway.id === variables.params.gateway_id)?.name;
|
||||
const gatewayName = gateways.find(
|
||||
(gateway) => gateway.id === variables.params.gateway_id,
|
||||
)?.name;
|
||||
if (gatewayName) {
|
||||
updateInstalledGatewayNames({
|
||||
skillId: variables.skillId,
|
||||
gatewayId: variables.params.gateway_id,
|
||||
gatewayName,
|
||||
installed: true,
|
||||
});
|
||||
@@ -277,11 +303,13 @@ export default function SkillsMarketplacePage() {
|
||||
...previous,
|
||||
[variables.params.gateway_id]: false,
|
||||
}));
|
||||
const gatewayName =
|
||||
gateways.find((gateway) => gateway.id === variables.params.gateway_id)?.name;
|
||||
const gatewayName = gateways.find(
|
||||
(gateway) => gateway.id === variables.params.gateway_id,
|
||||
)?.name;
|
||||
if (gatewayName) {
|
||||
updateInstalledGatewayNames({
|
||||
skillId: variables.skillId,
|
||||
gatewayId: variables.params.gateway_id,
|
||||
gatewayName,
|
||||
installed: false,
|
||||
});
|
||||
@@ -314,16 +342,22 @@ export default function SkillsMarketplacePage() {
|
||||
setGatewayStatusError(null);
|
||||
try {
|
||||
const gatewaySkills = await loadSkillsByGateway();
|
||||
const entries = gatewaySkills.map(({ gatewayId, skills: gatewaySkillRows }) => {
|
||||
const row = gatewaySkillRows.find((skill) => skill.id === selectedSkill.id);
|
||||
return [gatewayId, Boolean(row?.installed)] as const;
|
||||
});
|
||||
const entries = gatewaySkills.map(
|
||||
({ gatewayId, skills: gatewaySkillRows }) => {
|
||||
const row = gatewaySkillRows.find(
|
||||
(skill) => skill.id === selectedSkill.id,
|
||||
);
|
||||
return [gatewayId, Boolean(row?.installed)] as const;
|
||||
},
|
||||
);
|
||||
if (cancelled) return;
|
||||
setGatewayInstalledById(Object.fromEntries(entries));
|
||||
} catch (error) {
|
||||
if (cancelled) return;
|
||||
setGatewayStatusError(
|
||||
error instanceof Error ? error.message : "Unable to load gateway status.",
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Unable to load gateway status.",
|
||||
);
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
@@ -391,7 +425,9 @@ export default function SkillsMarketplacePage() {
|
||||
<div className="space-y-6">
|
||||
{gateways.length === 0 ? (
|
||||
<div className="rounded-xl border border-slate-200 bg-white p-6 text-sm text-slate-600 shadow-sm">
|
||||
<p className="font-medium text-slate-900">No gateways available yet.</p>
|
||||
<p className="font-medium text-slate-900">
|
||||
No gateways available yet.
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
Create a gateway first, then return here to manage installs.
|
||||
</p>
|
||||
@@ -407,7 +443,9 @@ export default function SkillsMarketplacePage() {
|
||||
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm">
|
||||
<MarketplaceSkillsTable
|
||||
skills={visibleSkills}
|
||||
installedGatewayNamesBySkillId={installedGatewayNamesBySkillId}
|
||||
installedGatewayNamesBySkillId={
|
||||
installedGatewayNamesBySkillId
|
||||
}
|
||||
isLoading={skillsQuery.isLoading}
|
||||
sorting={sorting}
|
||||
onSortingChange={onSortingChange}
|
||||
@@ -416,7 +454,8 @@ export default function SkillsMarketplacePage() {
|
||||
onSkillClick={setSelectedSkill}
|
||||
emptyState={{
|
||||
title: "No marketplace skills yet",
|
||||
description: "Add packs first, then synced skills will appear here.",
|
||||
description:
|
||||
"Add packs first, then synced skills will appear here.",
|
||||
actionHref: "/skills/packs/new",
|
||||
actionLabel: "Add your first pack",
|
||||
}}
|
||||
@@ -431,7 +470,9 @@ export default function SkillsMarketplacePage() {
|
||||
{packsQuery.error ? (
|
||||
<p className="text-sm text-rose-600">{packsQuery.error.message}</p>
|
||||
) : null}
|
||||
{mutationError ? <p className="text-sm text-rose-600">{mutationError}</p> : null}
|
||||
{mutationError ? (
|
||||
<p className="text-sm text-rose-600">{mutationError}</p>
|
||||
) : null}
|
||||
</div>
|
||||
</DashboardPageLayout>
|
||||
|
||||
|
||||
@@ -36,11 +36,10 @@ export default function EditSkillPackPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const pack = (
|
||||
packQuery.data?.status === 200 ? packQuery.data.data : null
|
||||
);
|
||||
const pack = packQuery.data?.status === 200 ? packQuery.data.data : null;
|
||||
|
||||
const saveMutation = useUpdateSkillPackApiV1SkillsPacksPackIdPatch<ApiError>();
|
||||
const saveMutation =
|
||||
useUpdateSkillPackApiV1SkillsPacksPackIdPatch<ApiError>();
|
||||
|
||||
return (
|
||||
<DashboardPageLayout
|
||||
|
||||
@@ -80,19 +80,18 @@ export default function SkillsPacksPage() {
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
const syncMutation =
|
||||
useSyncSkillPackApiV1SkillsPacksPackIdSyncPost<ApiError>(
|
||||
{
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: packsQueryKey,
|
||||
});
|
||||
},
|
||||
const syncMutation = useSyncSkillPackApiV1SkillsPacksPackIdSyncPost<ApiError>(
|
||||
{
|
||||
mutation: {
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: packsQueryKey,
|
||||
});
|
||||
},
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
},
|
||||
queryClient,
|
||||
);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!deleteTarget) return;
|
||||
@@ -113,7 +112,9 @@ export default function SkillsPacksPage() {
|
||||
const response = await syncMutation.mutateAsync({
|
||||
packId: pack.id,
|
||||
});
|
||||
setSyncWarnings(response.data.warnings ?? []);
|
||||
if (response.status === 200) {
|
||||
setSyncWarnings(response.data.warnings ?? []);
|
||||
}
|
||||
} finally {
|
||||
setSyncingPackIds((previous) => {
|
||||
const next = new Set(previous);
|
||||
@@ -124,7 +125,12 @@ export default function SkillsPacksPage() {
|
||||
};
|
||||
|
||||
const handleSyncAllPacks = async () => {
|
||||
if (!isAdmin || isSyncingAll || syncingPackIds.size > 0 || packs.length === 0) {
|
||||
if (
|
||||
!isAdmin ||
|
||||
isSyncingAll ||
|
||||
syncingPackIds.size > 0 ||
|
||||
packs.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,10 +151,12 @@ export default function SkillsPacksPage() {
|
||||
|
||||
try {
|
||||
const response = await syncMutation.mutateAsync({ packId: pack.id });
|
||||
setSyncWarnings((previous) => [
|
||||
...previous,
|
||||
...(response.data.warnings ?? []),
|
||||
]);
|
||||
if (response.status === 200) {
|
||||
setSyncWarnings((previous) => [
|
||||
...previous,
|
||||
...(response.data.warnings ?? []),
|
||||
]);
|
||||
}
|
||||
} catch {
|
||||
hasFailure = true;
|
||||
} finally {
|
||||
@@ -190,9 +198,7 @@ export default function SkillsPacksPage() {
|
||||
size: "md",
|
||||
})}
|
||||
disabled={
|
||||
isSyncingAll ||
|
||||
syncingPackIds.size > 0 ||
|
||||
packs.length === 0
|
||||
isSyncingAll || syncingPackIds.size > 0 || packs.length === 0
|
||||
}
|
||||
onClick={() => {
|
||||
void handleSyncAllPacks();
|
||||
@@ -241,10 +247,14 @@ export default function SkillsPacksPage() {
|
||||
<p className="text-sm text-rose-600">{packsQuery.error.message}</p>
|
||||
) : null}
|
||||
{deleteMutation.error ? (
|
||||
<p className="text-sm text-rose-600">{deleteMutation.error.message}</p>
|
||||
<p className="text-sm text-rose-600">
|
||||
{deleteMutation.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
{syncMutation.error ? (
|
||||
<p className="text-sm text-rose-600">{syncMutation.error.message}</p>
|
||||
<p className="text-sm text-rose-600">
|
||||
{syncMutation.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
{syncAllError ? (
|
||||
<p className="text-sm text-rose-600">{syncAllError}</p>
|
||||
|
||||
@@ -177,7 +177,8 @@ export function DashboardSidebar() {
|
||||
href="/skills/marketplace"
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
|
||||
pathname === "/skills" || pathname.startsWith("/skills/marketplace")
|
||||
pathname === "/skills" ||
|
||||
pathname.startsWith("/skills/marketplace")
|
||||
? "bg-blue-100 text-blue-800 font-medium"
|
||||
: "hover:bg-slate-100",
|
||||
)}
|
||||
|
||||
@@ -11,9 +11,13 @@ import {
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import type { MarketplaceSkillCardRead } from "@/api/generated/model";
|
||||
import { DataTable, type DataTableEmptyState } from "@/components/tables/DataTable";
|
||||
import {
|
||||
DataTable,
|
||||
type DataTableEmptyState,
|
||||
} from "@/components/tables/DataTable";
|
||||
import { dateCell } from "@/components/tables/cell-formatters";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
SKILLS_TABLE_EMPTY_ICON,
|
||||
useTableSortingState,
|
||||
@@ -25,9 +29,32 @@ import {
|
||||
packsHrefFromPackUrl,
|
||||
} from "@/lib/skills-source";
|
||||
|
||||
function riskBadgeVariant(risk: string | null | undefined) {
|
||||
const normalizedRisk = (risk || "unknown").trim().toLowerCase();
|
||||
|
||||
switch (normalizedRisk) {
|
||||
case "low":
|
||||
return "success";
|
||||
case "medium":
|
||||
case "moderate":
|
||||
return "warning";
|
||||
case "high":
|
||||
case "critical":
|
||||
return "danger";
|
||||
case "unknown":
|
||||
return "outline";
|
||||
default:
|
||||
return "accent";
|
||||
}
|
||||
}
|
||||
|
||||
function riskBadgeLabel(risk: string | null | undefined) {
|
||||
return (risk || "unknown").trim() || "unknown";
|
||||
}
|
||||
|
||||
type MarketplaceSkillsTableProps = {
|
||||
skills: MarketplaceSkillCardRead[];
|
||||
installedGatewayNamesBySkillId?: Record<string, string[]>;
|
||||
installedGatewayNamesBySkillId?: Record<string, { id: string; name: string }[]>;
|
||||
isLoading?: boolean;
|
||||
sorting?: SortingState;
|
||||
onSortingChange?: OnChangeFn<SortingState>;
|
||||
@@ -78,7 +105,9 @@ export function MarketplaceSkillsTable({
|
||||
{row.original.name}
|
||||
</button>
|
||||
) : (
|
||||
<p className="text-sm font-medium text-slate-900">{row.original.name}</p>
|
||||
<p className="text-sm font-medium text-slate-900">
|
||||
{row.original.name}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
className="mt-1 line-clamp-2 text-xs text-slate-500"
|
||||
@@ -117,9 +146,12 @@ export function MarketplaceSkillsTable({
|
||||
accessorKey: "risk",
|
||||
header: "Risk",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-sm text-slate-700">
|
||||
{row.original.risk || "unknown"}
|
||||
</span>
|
||||
<Badge
|
||||
variant={riskBadgeVariant(row.original.risk)}
|
||||
className="px-2 py-0.5"
|
||||
>
|
||||
{riskBadgeLabel(row.original.risk)}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -127,6 +159,11 @@ export function MarketplaceSkillsTable({
|
||||
header: "Source",
|
||||
cell: ({ row }) => {
|
||||
const sourceHref = row.original.source || row.original.source_url;
|
||||
|
||||
if (!sourceHref) {
|
||||
return <span className="text-sm text-slate-400">No source</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={sourceHref}
|
||||
@@ -145,15 +182,32 @@ export function MarketplaceSkillsTable({
|
||||
header: "Installed On",
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
const installedOn = installedGatewayNamesBySkillId?.[row.original.id] ?? [];
|
||||
const installedOn =
|
||||
installedGatewayNamesBySkillId?.[row.original.id] ?? [];
|
||||
if (installedOn.length === 0) {
|
||||
return <span className="text-sm text-slate-500">-</span>;
|
||||
}
|
||||
const installedOnText = installedOn.join(", ");
|
||||
return (
|
||||
<span className="text-sm text-slate-700" title={installedOnText}>
|
||||
{installedOnText}
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{installedOn.map((gateway, index) => {
|
||||
const isLast = index === installedOn.length - 1;
|
||||
return (
|
||||
<span
|
||||
key={`${gateway.id}-${index}`}
|
||||
className="inline-flex items-center gap-1 text-sm text-slate-700"
|
||||
title={gateway.name}
|
||||
>
|
||||
<Link
|
||||
href={`/gateways/${gateway.id}`}
|
||||
className="text-blue-700 hover:text-blue-600 hover:underline"
|
||||
>
|
||||
{gateway.name}
|
||||
</Link>
|
||||
{!isLast ? "," : ""}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -48,7 +48,9 @@ export function SkillInstallDialog({
|
||||
className="max-w-xl p-6 sm:p-7"
|
||||
>
|
||||
<DialogHeader className="pb-1">
|
||||
<DialogTitle>{selectedSkill ? selectedSkill.name : "Install skill"}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{selectedSkill ? selectedSkill.name : "Install skill"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Choose one or more gateways where this skill should be installed.
|
||||
</DialogDescription>
|
||||
@@ -60,14 +62,17 @@ export function SkillInstallDialog({
|
||||
) : (
|
||||
gateways.map((gateway) => {
|
||||
const isInstalled = gatewayInstalledById[gateway.id] === true;
|
||||
const isUpdatingGateway = installingGatewayId === gateway.id && isMutating;
|
||||
const isUpdatingGateway =
|
||||
installingGatewayId === gateway.id && isMutating;
|
||||
return (
|
||||
<div
|
||||
key={gateway.id}
|
||||
className="flex items-center justify-between rounded-xl border border-slate-200 bg-white p-4"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-900">{gateway.name}</p>
|
||||
<p className="text-sm font-medium text-slate-900">
|
||||
{gateway.name}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -91,11 +96,17 @@ export function SkillInstallDialog({
|
||||
{gatewayStatusError ? (
|
||||
<p className="text-sm text-rose-600">{gatewayStatusError}</p>
|
||||
) : null}
|
||||
{mutationError ? <p className="text-sm text-rose-600">{mutationError}</p> : null}
|
||||
{mutationError ? (
|
||||
<p className="text-sm text-rose-600">{mutationError}</p>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="mt-6 border-t border-slate-200 pt-4">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isMutating}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
disabled={isMutating}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import type { SkillPackRead } from "@/api/generated/model";
|
||||
import { DataTable, type DataTableEmptyState } from "@/components/tables/DataTable";
|
||||
import {
|
||||
DataTable,
|
||||
type DataTableEmptyState,
|
||||
} from "@/components/tables/DataTable";
|
||||
import { dateCell } from "@/components/tables/cell-formatters";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -62,7 +65,9 @@ export function SkillPacksTable({
|
||||
header: "Pack",
|
||||
cell: ({ row }) => (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-900">{row.original.name}</p>
|
||||
<p className="text-sm font-medium text-slate-900">
|
||||
{row.original.name}
|
||||
</p>
|
||||
<p className="mt-1 line-clamp-2 text-xs text-slate-500">
|
||||
{row.original.description || "No description provided."}
|
||||
</p>
|
||||
@@ -86,7 +91,11 @@ export function SkillPacksTable({
|
||||
{
|
||||
accessorKey: "branch",
|
||||
header: "Branch",
|
||||
cell: ({ row }) => <p className="text-sm text-slate-900">{row.original.branch || "main"}</p>,
|
||||
cell: ({ row }) => (
|
||||
<p className="text-sm text-slate-900">
|
||||
{row.original.branch || "main"}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "skill_count",
|
||||
@@ -111,7 +120,9 @@ export function SkillPacksTable({
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
if (!onSync) return null;
|
||||
const isThisPackSyncing = Boolean(syncingPackIds?.has(row.original.id));
|
||||
const isThisPackSyncing = Boolean(
|
||||
syncingPackIds?.has(row.original.id),
|
||||
);
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
|
||||
@@ -34,7 +34,8 @@ export const useTableSortingState = (
|
||||
resolvedSorting: SortingState;
|
||||
handleSortingChange: OnChangeFn<SortingState>;
|
||||
} => {
|
||||
const [internalSorting, setInternalSorting] = useState<SortingState>(defaultSorting);
|
||||
const [internalSorting, setInternalSorting] =
|
||||
useState<SortingState>(defaultSorting);
|
||||
const resolvedSorting = sorting ?? internalSorting;
|
||||
const handleSortingChange: OnChangeFn<SortingState> =
|
||||
onSortingChange ??
|
||||
|
||||
@@ -3,7 +3,9 @@ export const normalizeRepoSourceUrl = (sourceUrl: string): string => {
|
||||
return trimmed.endsWith(".git") ? trimmed.slice(0, -4) : trimmed;
|
||||
};
|
||||
|
||||
export const repoBaseFromSkillSourceUrl = (skillSourceUrl: string): string | null => {
|
||||
export const repoBaseFromSkillSourceUrl = (
|
||||
skillSourceUrl: string,
|
||||
): string | null => {
|
||||
try {
|
||||
const parsed = new URL(skillSourceUrl);
|
||||
const marker = "/tree/";
|
||||
@@ -11,7 +13,8 @@ export const repoBaseFromSkillSourceUrl = (skillSourceUrl: string): string | nul
|
||||
if (markerIndex <= 0) return null;
|
||||
|
||||
// Reject unexpected structures (e.g. multiple /tree/ markers).
|
||||
if (parsed.pathname.indexOf(marker, markerIndex + marker.length) !== -1) return null;
|
||||
if (parsed.pathname.indexOf(marker, markerIndex + marker.length) !== -1)
|
||||
return null;
|
||||
|
||||
const repoPath = parsed.pathname.slice(0, markerIndex);
|
||||
if (!repoPath || repoPath === "/") return null;
|
||||
|
||||
Reference in New Issue
Block a user