Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function ManagementTools({
)}

{/* Manage package */}
{perms.can_manage && (
{(perms.can_manage || perms.can_moderate) && (
<div className="package-listing-management-tools__island">
<NewButton
csSize="small"
Expand Down
4 changes: 4 additions & 0 deletions apps/cyberstorm-remix/app/p/packageEdit.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@
.package-edit__save-button {
flex: 1 0 0;
}

.package-edit__skeleton {
min-height: 20rem;
}
}
133 changes: 47 additions & 86 deletions apps/cyberstorm-remix/app/p/packageEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
NewIcon,
NewSelectSearch,
NewTag,
SkeletonBox,
formatToDisplayName,
useToast,
} from "@thunderstore/cyberstorm";
Expand All @@ -17,20 +18,17 @@ import {
type PackageListingUpdateRequestData,
packageUnlist,
} from "@thunderstore/thunderstore-api";
import { DapperTs } from "@thunderstore/dapper-ts";
import { type OutletContextShape } from "~/root";
import {
getPublicEnvVariables,
getSessionTools,
} from "cyberstorm/security/publicEnvVariables";
import { PageHeader } from "~/commonComponents/PageHeader/PageHeader";
import { useStrongForm } from "cyberstorm/utils/StrongForm/useStrongForm";
import { useReducer } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBan, faCheck } from "@fortawesome/pro-solid-svg-icons";
import { ApiAction } from "@thunderstore/ts-api-react-actions";
import { getDapperForRequest } from "cyberstorm/utils/dapperSingleton";
import { getPrivateListing } from "app/p/listingUtils";

export const meta: MetaFunction<typeof loader> = ({ data }) => {
export const meta: MetaFunction<typeof clientLoader> = ({ data }) => {
return [
{
title: data
Expand All @@ -40,92 +38,49 @@ export const meta: MetaFunction<typeof loader> = ({ data }) => {
];
};

const package404 = new Response("Package not found", { status: 404 });

export async function loader({ params }: LoaderFunctionArgs) {
if (params.communityId && params.namespaceId && params.packageId) {
try {
const publicEnvVariables = getPublicEnvVariables(["VITE_API_URL"]);
const dapper = new DapperTs(() => {
return {
apiHost: publicEnvVariables.VITE_API_URL,
sessionId: undefined,
};
});
return {
community: await dapper.getCommunity(params.communityId),
communityFilters: await dapper.getCommunityFilters(params.communityId),
listing: await dapper.getPackageListingDetails(
params.communityId,
params.namespaceId,
params.packageId
),
team: await dapper.getTeamDetails(params.namespaceId),
filters: await dapper.getCommunityFilters(params.communityId),
permissions: undefined,
};
} catch (error) {
if (error instanceof ApiError) {
throw new Response("Package not found", { status: 404 });
} else {
// REMIX TODO: Add sentry
throw error;
}
}
}
throw new Response("Package not found", { status: 404 });
return undefined;
}

// TODO: Needs to check if package is available for the logged in user
export async function clientLoader({ params }: LoaderFunctionArgs) {
if (params.communityId && params.namespaceId && params.packageId) {
try {
const tools = getSessionTools();
const dapper = new DapperTs(() => {
return {
apiHost: tools?.getConfig().apiHost,
sessionId: tools?.getConfig().sessionId,
};
});
export async function clientLoader({ params, request }: LoaderFunctionArgs) {
const { communityId, namespaceId, packageId } = params;

const permissions = await dapper.getPackagePermissions(
params.communityId,
params.namespaceId,
params.packageId
);
if (!communityId || !namespaceId || !packageId) {
throw package404;
}

if (!permissions?.permissions.can_manage) {
throw new Response("Unauthorized", { status: 403 });
}
try {
const dapper = getDapperForRequest(request);

return {
community: await dapper.getCommunity(params.communityId),
communityFilters: await dapper.getCommunityFilters(params.communityId),
listing: await dapper.getPackageListingDetails(
params.communityId,
params.namespaceId,
params.packageId
),
team: await dapper.getTeamDetails(params.namespaceId),
filters: await dapper.getCommunityFilters(params.communityId),
permissions: permissions,
};
} catch (error) {
if (error instanceof ApiError) {
throw new Response("Package not found", { status: 404 });
} else {
throw error;
}
// Fetch everything at once for faster page load, even if we may
// overfetch in case we lack authentication/authorization.
const [listing, permissions, filters] = await Promise.all([
getPrivateListing(dapper, { communityId, namespaceId, packageId }),
dapper.getPackagePermissions(communityId, namespaceId, packageId),
dapper.getCommunityFilters(communityId),
]);

if (!permissions) {
throw new Response("Unauthenticated", { status: 401 });
} else if (
!permissions.permissions.can_manage &&
!permissions.permissions.can_moderate
) {
throw new Response("Unauthorized", { status: 403 });
}

return { listing, permissions, filters };
} catch (error) {
throw error instanceof ApiError ? package404 : error;
}
throw new Response("Package not found", { status: 404 });
}

clientLoader.hydrate = true;

export default function PackageListing() {
const { community, listing, filters, permissions } = useLoaderData<
typeof loader | typeof clientLoader
>();

const loaderData = useLoaderData<typeof loader | typeof clientLoader>();
const outletContext = useOutletContext() as OutletContextShape;
const config = outletContext.requestConfig;
const toast = useToast();
Expand Down Expand Up @@ -185,7 +140,7 @@ export default function PackageListing() {
}

const [formInputs, updateFormFieldState] = useReducer(formFieldUpdateAction, {
categories: listing.categories.map((c) => c.slug),
categories: loaderData?.listing.categories.map((c) => c.slug) ?? [],
});

type SubmitorOutput = Awaited<ReturnType<typeof packageListingUpdate>>;
Expand All @@ -194,7 +149,7 @@ export default function PackageListing() {
return await packageListingUpdate({
config: config,
params: {
community: community.identifier,
community: listing.community_identifier,
namespace: listing.namespace,
package: listing.name,
},
Expand Down Expand Up @@ -233,14 +188,20 @@ export default function PackageListing() {
},
});

if (!loaderData) {
return <SkeletonBox className="package-edit__skeleton" />;
}

const { listing, filters, permissions } = loaderData;

return (
<>
<PageHeader headingLevel="1" headingSize="2">
Edit package
</PageHeader>
<div className="package-edit__main">
<section className="package-edit__section">
{permissions?.permissions.can_unlist ? (
{permissions.permissions.can_unlist && (
<>
<div className="package-edit__row">
<div className="package-edit__info">
Expand All @@ -262,7 +223,7 @@ export default function PackageListing() {
unlistAction({
config: config,
params: {
community: community.identifier,
community: listing.community_identifier,
namespace: listing.namespace,
package: listing.name,
},
Expand All @@ -280,8 +241,8 @@ export default function PackageListing() {
</div>
<div className="package-edit__divider" />
</>
) : null}
{permissions?.permissions.can_manage_deprecation ? (
)}
{permissions.permissions.can_manage_deprecation && (
<>
<div className="package-edit__row">
<div className="package-edit__info">
Expand Down Expand Up @@ -331,7 +292,7 @@ export default function PackageListing() {
</div>
<div className="package-edit__divider" />
</>
) : null}
)}
<div className="package-edit__row">
<div className="package-edit__info">
<div className="package-edit__title">Categories</div>
Expand Down
Loading