Skip to content
Merged
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
@@ -0,0 +1,24 @@
@layer nimbus-layout {
.report-package__block {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-start;
align-self: stretch;
}

.report-package__label {
color: var(--color-text-primary);
font-weight: var(--font-weight-regular);
font-size: var(--font-size-body-lg);
font-style: normal;
line-height: var(--line-height-md);
}

.report-package__textarea {
> textarea {
width: 100%;
height: 10rem;
}
}
}
204 changes: 204 additions & 0 deletions apps/cyberstorm-remix/app/p/components/ReportPackage/ReportPackage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { useEffect, useReducer, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFlagSwallowtail } from "@fortawesome/pro-solid-svg-icons";

import {
Modal,
NewButton,
NewIcon,
NewSelect,
NewTextInput,
type SelectOption,
useToast,
} from "@thunderstore/cyberstorm";
import {
type RequestConfig,
type PackageListingReportRequestData,
packageListingReport,
} from "@thunderstore/thunderstore-api";

import { useStrongForm } from "cyberstorm/utils/StrongForm/useStrongForm";
import "./ReportPackage.css";

const reportOptions: SelectOption<PackageListingReportRequestData["reason"]>[] =
[
{ value: "Spam", label: "Spam" },
{ value: "Malware", label: "Malware" },
{ value: "Reupload", label: "Reupload" },
{ value: "CopyrightOrLicense", label: "Copyright Or License" },
{ value: "WrongCommunity", label: "Wrong Community" },
{ value: "WrongCategories", label: "Wrong Categories" },
{ value: "Other", label: "Other" },
];

function ReportPackageButton(props: { onClick: () => void }) {
return (
<NewButton
onClick={props.onClick}
tooltipText="Report Package"
csVariant="secondary"
csModifiers={["only-icon"]}
>
<NewIcon csMode="inline" noWrapper>
<FontAwesomeIcon icon={faFlagSwallowtail} />
</NewIcon>
</NewButton>
);
}

ReportPackageButton.displayName = "ReportPackageButton";

interface ReportPackageFormProps {
community: string;
namespace: string;
package: string;
config: () => RequestConfig;
toast: ReturnType<typeof useToast>;
}

interface ReportPackageModalProps {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
}

function ReportPackageForm(
props: ReportPackageFormProps & ReportPackageModalProps
) {
const { config, toast, isOpen, setIsOpen, ...requestParams } = props;

function formFieldUpdateAction(
state: PackageListingReportRequestData,
action: {
field: keyof PackageListingReportRequestData;
value: PackageListingReportRequestData[keyof PackageListingReportRequestData];
}
) {
return {
...state,
[action.field]: action.value,
};
}

const [formInputs, updateFormFieldState] = useReducer(formFieldUpdateAction, {
reason: "Other",
description: "",
});

type SubmitorOutput = Awaited<ReturnType<typeof packageListingReport>>;

async function submitor(data: typeof formInputs): Promise<SubmitorOutput> {
return await packageListingReport({
config: config,
params: requestParams,
queryParams: {},
data: { reason: data.reason, description: data.description },
});
}

type InputErrors = {
[key in keyof typeof formInputs]?: string | string[];
};

const strongForm = useStrongForm<
typeof formInputs,
PackageListingReportRequestData,
Error,
SubmitorOutput,
Error,
InputErrors
>({
inputs: formInputs,
submitor,
onSubmitSuccess: () => {
toast.addToast({
csVariant: "success",
children: `Package reported`,
duration: 4000,
});
setIsOpen(false);
},
onSubmitError: (error) => {
toast.addToast({
csVariant: "danger",
children: `Error occurred: ${error.message || "Unknown error"}`,
duration: 8000,
});
},
});

return (
<Modal
titleContent="Report Package"
csSize="small"
open={props.isOpen}
onOpenChange={props.setIsOpen}
>
<Modal.Body>
<div className="report-package__block">
<p className="report-package__label">Reason</p>
<NewSelect
name={"reason"}
options={reportOptions}
placeholder="Please select..."
value={formInputs.reason}
onChange={(value) => {
updateFormFieldState({ field: "reason", value: value });
}}
id="reason"
csSize="small"
/>
</div>
<div className="report-package__block">
<p className="report-package__label">
Additional information (optional)
</p>
<NewTextInput
value={formInputs.description || ""}
onChange={(e) => {
updateFormFieldState({
field: "description",
value: e.target.value,
});
}}
placeholder="Invalid submission"
csSize="textarea"
rootClasses="report-package__textarea"
/>
</div>
</Modal.Body>
<Modal.Footer>
<NewButton csVariant="success" onClick={strongForm.submit}>
Submit
</NewButton>
</Modal.Footer>
</Modal>
);
}

ReportPackageForm.displayName = "ReportPackageForm";

export function useReportPackage(formProps: Promise<ReportPackageFormProps>) {
const [isOpen, setIsOpen] = useState(false);
const [props, setProps] = useState<ReportPackageFormProps | null>(null);

async function awaitAndSetProps() {
if (!props) {
setProps(await formProps);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the formProps here is a Promise and awaitAndSetProps is called from useEffect. Should potential Promise rejections be handled in useEffect?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented on this in the commit message. The page where the reporting feat resides currently renders a 500 error page if the promise rejects, so I chose to ignore it here too. Do you find that reasonable?

}
}

useEffect(() => {
awaitAndSetProps();
}, [formProps, props]);

const button = <ReportPackageButton onClick={() => setIsOpen(true)} />;

const form = props && (
<ReportPackageForm {...{ isOpen, setIsOpen }} {...props} />
);

return {
ReportPackageButton: button,
ReportPackageForm: form,
};
}
30 changes: 0 additions & 30 deletions apps/cyberstorm-remix/app/p/packageListing.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,36 +76,6 @@
justify-content: space-between;
}

.report-package__body {
align-items: stretch;
}

.report-package__block {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}

.report-package__label {
color: var(--color-text-primary);
font-weight: var(--font-weight-regular);
font-size: var(--font-size-body-lg);
font-style: normal;
line-height: var(--line-height-md);
}

.report-package__textarea {
> textarea {
width: 100%;
height: 10rem;
}
}

.report-package__footer {
justify-content: flex-end;
}

.package-listing__package-content-section {
display: flex;
flex: 1 0 0;
Expand Down
Loading
Loading