diff --git a/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/page.tsx b/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/page.tsx
index cabb209d1..03765c9e6 100644
--- a/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/page.tsx
+++ b/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/page.tsx
@@ -1,10 +1,12 @@
"use client";
-import { Alert, Button, Tag, TextInput } from "@thunderstore/cyberstorm";
-import styles from "./PackageManagementForm.module.css";
+import { Button } from "@thunderstore/cyberstorm";
import { useDapper } from "@thunderstore/dapper";
import { usePromise } from "@thunderstore/use-promise";
-import { faCircleExclamation } from "@fortawesome/pro-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import {
+ PackageDeprecateAction,
+ PackageEditForm,
+} from "@thunderstore/cyberstorm-forms";
+import { useState } from "react";
export default function Page({
params,
@@ -12,49 +14,59 @@ export default function Page({
params: { community: string; namespace: string; package: string };
}) {
const dapper = useDapper();
- const packageData = usePromise(dapper.getPackageListingDetails, [
+ const packageDataInitial = usePromise(dapper.getPackageListingDetails, [
params.community,
params.namespace,
params.package,
]);
+ const communityData = usePromise(dapper.getCommunityFilters, [
+ params.community,
+ ]);
+ const options = communityData.package_categories.map((cat) => {
+ return { label: cat.name, value: cat.slug };
+ });
+
+ const [packageData, setPackageData] = useState(packageDataInitial);
+
+ // TODO: Convert to using usePromise's cache when it can handle manual busts
+ // Or React Query stuff
+ async function useUpdatePackageData() {
+ const dapper = useDapper();
+ const packageDataUpdate = await dapper.getPackageListingDetails(
+ params.community,
+ params.namespace,
+ params.package
+ );
+ setPackageData(packageDataUpdate);
+ }
return (
-
-
-
}
- content={
- "Changes might take several minutes to show publicly! Info shown below is always up to date."
- }
- variant="info"
- />
-
Package status
-
-
-
-
-
-
- {packageData.is_deprecated ? (
-
+ cat.slug)}
+ isDeprecated={packageData.is_deprecated}
+ deprecationButton={
+
+ {packageData.is_deprecated ? (
Undeprecate
-
- ) : (
-
+ ) : (
Deprecate
-
- )}
-
- Save changes
+ )}
-
-
+ }
+ />
);
}
diff --git a/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageDetail/layout.tsx b/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageDetail/layout.tsx
index c8a70c75a..5bfa2e087 100644
--- a/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageDetail/layout.tsx
+++ b/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageDetail/layout.tsx
@@ -41,6 +41,8 @@ export default function PackageDetailsLayout({
currentUser.rated_packages.includes(packageData.uuid4)
);
+ // TODO: Convert to using usePromise's cache when it can handle manual busts
+ // Or React Query stuff
async function useUpdateLikeStatus() {
const dapper = useDapper();
const currentUser = await dapper.getCurrentUser();
@@ -73,6 +75,7 @@ export default function PackageDetailsLayout({
) : null}
Promise;
+}) {
+ const { onSubmitSuccess, onSubmitError } = useFormToaster({
+ successMessage: `${
+ props.isDeprecated ? "Undeprecated" : "Deprecated"
+ } package ${props.packageName}`,
+ });
+
+ function onActionSuccess() {
+ props.packageDataUpdateTrigger();
+ onSubmitSuccess();
+ }
+
+ function onActionError() {
+ onSubmitError();
+ }
+
+ const onSubmit = ApiAction({
+ schema: PackageDeprecateActionSchema,
+ meta: { packageName: props.packageName, namespace: props.namespace },
+ endpoint: packageDeprecate,
+ onSubmitSuccess: onActionSuccess,
+ onSubmitError: onActionError,
+ });
+
+ return function () {
+ onSubmit({ is_deprecated: !props.isDeprecated });
+ };
+}
+
+PackageDeprecateAction.displayName = "PackageDeprecateAction";
diff --git a/packages/cyberstorm-forms/src/actions/PackageLikeAction.tsx b/packages/cyberstorm-forms/src/actions/PackageLikeAction.tsx
index 8f6e218cf..40ebb7f05 100644
--- a/packages/cyberstorm-forms/src/actions/PackageLikeAction.tsx
+++ b/packages/cyberstorm-forms/src/actions/PackageLikeAction.tsx
@@ -8,6 +8,7 @@ import {
import { packageLike } from "@thunderstore/thunderstore-api";
export function PackageLikeAction(props: {
+ isLoggedIn: boolean;
packageName: string;
uuid4: string;
isLiked: boolean;
@@ -17,6 +18,9 @@ export function PackageLikeAction(props: {
successMessage: `${props.isLiked ? "Unliked" : "Liked"} package ${
props.packageName
}`,
+ errorMessage: props.isLoggedIn
+ ? "Unknown error occurred. The error has been logged"
+ : "You must be logged in to like a package!",
});
function onActionSuccess() {
diff --git a/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/PackageManagementForm.module.css b/packages/cyberstorm-forms/src/forms/PackageEditForm.module.css
similarity index 50%
rename from apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/PackageManagementForm.module.css
rename to packages/cyberstorm-forms/src/forms/PackageEditForm.module.css
index ad8dfc16c..99dd89f37 100644
--- a/apps/cyberstorm-nextjs/app/c/[community]/(package)/@tspackage/p/[namespace]/(packageRoute)/[package]/(pkg)/@packageCard/@packageManagement/PackageManagementForm.module.css
+++ b/packages/cyberstorm-forms/src/forms/PackageEditForm.module.css
@@ -2,10 +2,20 @@
display: flex;
flex-direction: column;
gap: var(--gap--32);
- max-height: 70%;
}
-.section {
+.main {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space--32);
+ padding: var(--space--32);
+}
+
+.dialog {
+ border-top: var(--border-width--px) solid var(--color-surface--5);
+}
+
+.statusSection {
display: flex;
flex-direction: column;
gap: var(--gap--16);
@@ -23,6 +33,9 @@
.footer {
display: flex;
- flex-direction: row;
+ flex-direction: row-reverse;
+ gap: 1rem;
justify-content: space-between;
+ padding: var(--space--16) var(--space--24);
+ border-top: var(--border-width--px) solid var(--color-surface--5);
}
diff --git a/packages/cyberstorm-forms/src/forms/PackageEditForm.tsx b/packages/cyberstorm-forms/src/forms/PackageEditForm.tsx
new file mode 100644
index 000000000..bcbc02a53
--- /dev/null
+++ b/packages/cyberstorm-forms/src/forms/PackageEditForm.tsx
@@ -0,0 +1,99 @@
+"use client";
+
+import styles from "./PackageEditForm.module.css";
+import { packageEditCategories } from "@thunderstore/thunderstore-api";
+import {
+ ApiForm,
+ packageEditFormSchema,
+} from "@thunderstore/ts-api-react-forms";
+import {
+ FormMultiSelectSearch,
+ FormSubmitButton,
+ useFormToaster,
+} from "@thunderstore/cyberstorm-forms";
+import { Alert, MultiSelectSearchOption, Tag } from "@thunderstore/cyberstorm";
+import { ReactNode } from "react";
+import { faCircleExclamation } from "@fortawesome/pro-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+// TODO: Separate deprecation action from this form
+// when we move away from the modal
+// Package deprecation is done for the package, while other edits
+// are for the package listing
+export function PackageEditForm(props: {
+ dialogOnChange?: (v: boolean) => void;
+ options: MultiSelectSearchOption[];
+ community: string;
+ namespace: string;
+ package: string;
+ current_categories: string[];
+ isDeprecated: boolean;
+ deprecationButton: ReactNode;
+}) {
+ const { onSubmitSuccess, onSubmitError } = useFormToaster({
+ successMessage: "Changes saved!",
+ });
+
+ return (
+
+
+
+
+
}
+ content={
+ "Changes might take several minutes to show publicly! Info shown below is always up to date."
+ }
+ variant="info"
+ />
+
+
+
+
Edit categories
+
Current categories
+ {props.current_categories}
+
New categories
+
+ v.map((x) => x.value)
+ }
+ />
+
+
+
+
+ {props.deprecationButton}
+
+
+
+ );
+}
+
+PackageEditForm.displayName = "PackageEditForm";
diff --git a/packages/cyberstorm-forms/src/index.ts b/packages/cyberstorm-forms/src/index.ts
index f75356cff..3938927fe 100644
--- a/packages/cyberstorm-forms/src/index.ts
+++ b/packages/cyberstorm-forms/src/index.ts
@@ -1,6 +1,7 @@
export { useFormToaster } from "./useFormToaster";
export { TeamMemberChangeRoleAction } from "./actions/TeamMemberChangeRoleAction";
export { PackageLikeAction } from "./actions/PackageLikeAction";
+export { PackageDeprecateAction } from "./actions/PackageDeprecateAction";
export { FormSubmitButton } from "./components/FormSubmitButton";
export { FormSelectSearch } from "./components/FormSelectSearch";
export { FormMultiSelectSearch } from "./components/FormMultiSelectSearch";
@@ -15,3 +16,4 @@ export { RemoveServiceAccountForm } from "./forms/RemoveServiceAccountForm";
export { DeleteAccountForm } from "./forms/DeleteAccountForm";
export { DisbandTeamForm } from "./forms/DisbandTeamForm";
export { LeaveTeamForm } from "./forms/LeaveTeamForm";
+export { PackageEditForm } from "./forms/PackageEditForm";
diff --git a/packages/cyberstorm-forms/src/useFormToaster.ts b/packages/cyberstorm-forms/src/useFormToaster.ts
index ec89fe582..7e0e53254 100644
--- a/packages/cyberstorm-forms/src/useFormToaster.ts
+++ b/packages/cyberstorm-forms/src/useFormToaster.ts
@@ -4,6 +4,7 @@ import { useToast } from "@thunderstore/cyberstorm/src/components/Toast/Provider
export type UseFormToasterArgs = {
successMessage: string;
+ errorMessage?: string;
};
export type UseFormToasterReturn = {
onSubmitSuccess: () => void;
@@ -11,6 +12,7 @@ export type UseFormToasterReturn = {
};
export function useFormToaster({
successMessage,
+ errorMessage,
}: UseFormToasterArgs): UseFormToasterReturn {
const toast = useToast();
@@ -25,7 +27,9 @@ export function useFormToaster({
onSubmitError: () => {
toast.addToast({
variant: "danger",
- message: "Unknown error occurred. The error has been logged",
+ message: errorMessage
+ ? errorMessage
+ : "Unknown error occurred. The error has been logged",
});
},
};
diff --git a/packages/cyberstorm/src/components/CollapsibleText/CollapsibleText.module.css b/packages/cyberstorm/src/components/CollapsibleText/CollapsibleText.module.css
index 3dc709fe1..84a8240af 100644
--- a/packages/cyberstorm/src/components/CollapsibleText/CollapsibleText.module.css
+++ b/packages/cyberstorm/src/components/CollapsibleText/CollapsibleText.module.css
@@ -3,6 +3,7 @@
}
.text {
+ max-width: 45rem;
overflow: hidden;
color: var(--color-text--secondary);
font-size: var(--font-size--l);
diff --git a/packages/thunderstore-api/src/fetch/packageDeprecate.ts b/packages/thunderstore-api/src/fetch/packageDeprecate.ts
new file mode 100644
index 000000000..8003adfd8
--- /dev/null
+++ b/packages/thunderstore-api/src/fetch/packageDeprecate.ts
@@ -0,0 +1,29 @@
+import { RequestConfig } from "../index";
+import { apiFetch2 } from "../apiFetch";
+
+export type packageDeprecateMetaArgs = {
+ packageName: string;
+ namespace: string;
+};
+
+export type packageDeprecateApiArgs = {
+ is_deprecated: boolean;
+};
+
+export function packageDeprecate(
+ config: RequestConfig,
+ data: packageDeprecateApiArgs,
+ meta: packageDeprecateMetaArgs
+) {
+ const path = `/api/cyberstorm/package/${meta.namespace}/${meta.packageName}/deprecate/`;
+
+ return apiFetch2({
+ config,
+ path,
+ request: {
+ method: "POST",
+ cache: "no-store",
+ body: JSON.stringify(data),
+ },
+ });
+}
diff --git a/packages/thunderstore-api/src/fetch/packageEditCategories.ts b/packages/thunderstore-api/src/fetch/packageEditCategories.ts
new file mode 100644
index 000000000..8d9487529
--- /dev/null
+++ b/packages/thunderstore-api/src/fetch/packageEditCategories.ts
@@ -0,0 +1,34 @@
+import { RequestConfig } from "../index";
+import { apiFetch2 } from "../apiFetch";
+
+export type PackageEditMetaArgs = {
+ community: string;
+ namespace: string;
+ package: string;
+ current_categories: string[];
+};
+
+export type PackageEditApiArgs = {
+ new_categories: string[];
+};
+
+export function packageEditCategories(
+ config: RequestConfig,
+ data: PackageEditApiArgs,
+ meta: PackageEditMetaArgs
+) {
+ console.log("asdasdasd");
+ const path = `/api/cyberstorm/listing/${meta.community}/${meta.namespace}/${meta.package}/edit/categories/`;
+
+ return apiFetch2({
+ config,
+ path,
+ request: {
+ method: "POST",
+ body: JSON.stringify({
+ ...data,
+ current_categories: meta.current_categories,
+ }),
+ },
+ });
+}
diff --git a/packages/thunderstore-api/src/index.ts b/packages/thunderstore-api/src/index.ts
index 12cf16ecb..8c0fc8bad 100644
--- a/packages/thunderstore-api/src/index.ts
+++ b/packages/thunderstore-api/src/index.ts
@@ -30,5 +30,7 @@ export * from "./fetch/userDelete";
export * from "./fetch/teamDisbandTeam";
export * from "./fetch/teamEditMember";
export * from "./fetch/packageLike";
+export * from "./fetch/packageEditCategories";
+export * from "./fetch/packageDeprecate";
export * from "./fetch/teamRemoveMember";
export * from "./errors";
diff --git a/packages/ts-api-react-actions/src/index.ts b/packages/ts-api-react-actions/src/index.ts
index 49f3b6a0e..a032ccc7c 100644
--- a/packages/ts-api-react-actions/src/index.ts
+++ b/packages/ts-api-react-actions/src/index.ts
@@ -1,2 +1,3 @@
export { ApiAction } from "./ApiAction";
export { packageLikeActionSchema } from "./schema";
+export { PackageDeprecateActionSchema } from "./schema";
diff --git a/packages/ts-api-react-actions/src/schema.ts b/packages/ts-api-react-actions/src/schema.ts
index 82abb89e1..2b276660f 100644
--- a/packages/ts-api-react-actions/src/schema.ts
+++ b/packages/ts-api-react-actions/src/schema.ts
@@ -3,3 +3,7 @@ import { z } from "zod";
export const packageLikeActionSchema = z.object({
target_state: z.union([z.literal("rated"), z.literal("unrated")]),
});
+
+export const PackageDeprecateActionSchema = z.object({
+ is_deprecated: z.boolean(),
+});
diff --git a/packages/ts-api-react-forms/src/index.ts b/packages/ts-api-react-forms/src/index.ts
index c0f24f532..0160cc214 100644
--- a/packages/ts-api-react-forms/src/index.ts
+++ b/packages/ts-api-react-forms/src/index.ts
@@ -9,3 +9,4 @@ export { teamAddServiceAccountFormSchema } from "./schema";
export { userDeleteFormSchema } from "./schema";
export { teamDisbandFormSchema } from "./schema";
export { teamEditMemberFormSchema } from "./schema";
+export { packageEditFormSchema } from "./schema";
diff --git a/packages/ts-api-react-forms/src/schema.ts b/packages/ts-api-react-forms/src/schema.ts
index 94c31e5c0..825e2d184 100644
--- a/packages/ts-api-react-forms/src/schema.ts
+++ b/packages/ts-api-react-forms/src/schema.ts
@@ -47,3 +47,7 @@ export const teamDisbandFormSchema = z.object({
.string({ required_error: "Verification is required" })
.min(1, { message: "Verification is required" }),
});
+
+export const packageEditFormSchema = z.object({
+ new_categories: z.array(z.string()),
+});