Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make like button work #1067

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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 @@ -5,14 +5,15 @@ import {
faDonate,
faDownload,
faThumbsUp,
faFlag,
// faFlag,
faBoxes,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import styles from "./PackageDetailsLayout.module.css";
import { Button, Icon, Tag } from "@thunderstore/cyberstorm";
import { ReactNode, Suspense } from "react";
import { ReactNode, Suspense, useState } from "react";
import { WrapperCard } from "@thunderstore/cyberstorm/src/components/WrapperCard/WrapperCard";
import { PackageLikeAction } from "@thunderstore/cyberstorm-forms";

export default function PackageDetailsLayout({
packageMeta,
Expand All @@ -33,6 +34,22 @@ export default function PackageDetailsLayout({
params.namespace,
params.package,
]);
const teamData = usePromise(dapper.getTeamDetails, [params.namespace]);
const currentUser = usePromise(dapper.getCurrentUser, []);

const [isLiked, setIsLiked] = useState(
currentUser.rated_packages.includes(packageData.uuid4)
);

async function useUpdateLikeStatus() {
const dapper = useDapper();
const currentUser = await dapper.getCurrentUser();
if (currentUser.rated_packages.includes(packageData.uuid4)) {
setIsLiked(true);
} else {
setIsLiked(false);
}
}

const mappedPackageTagList = packageData.categories.map((category) => {
return (
Expand All @@ -51,9 +68,25 @@ export default function PackageDetailsLayout({
<a href={packageData.download_url} className={styles.download}>
<DownloadButton />
</a>
<DonateButton onClick={TODO} />
<LikeButton onClick={TODO} />
<ReportButton onClick={TODO} />
{teamData.donation_link ? (
<DonateButton donationLink={teamData.donation_link} />
) : null}
<Button.Root
onClick={PackageLikeAction({
packageName: params.package,
uuid4: packageData.uuid4,
isLiked: isLiked,
currentUserUpdateTrigger: useUpdateLikeStatus,
})}
tooltipText="Like"
colorScheme={isLiked ? "likeBlue" : "primary"}
paddingSize="mediumSquare"
>
<Button.ButtonIcon>
<FontAwesomeIcon icon={faThumbsUp} />
</Button.ButtonIcon>
</Button.Root>
{/* <ReportButton onClick={TODO} /> */}
</div>
<Suspense fallback={<p>TODO: SKELETON packageMeta</p>}>
{packageMeta}
Expand Down Expand Up @@ -82,28 +115,16 @@ export default function PackageDetailsLayout({
);
}

const TODO = () => Promise.resolve();
// const TODO = () => Promise.resolve();

interface Clickable {
onClick: () => Promise<void>;
}
// interface Clickable {
// onClick: () => Promise<void>;
// }

const LikeButton = (props: Clickable) => (
const DonateButton = (props: { donationLink: string }) => (
<Button.Root
onClick={props.onClick}
tooltipText="Like"
colorScheme="primary"
paddingSize="mediumSquare"
>
<Button.ButtonIcon>
<FontAwesomeIcon icon={faThumbsUp} />
</Button.ButtonIcon>
</Button.Root>
);

const DonateButton = (props: Clickable) => (
<Button.Root
onClick={props.onClick}
plain
href={props.donationLink}
tooltipText="Donate to author"
colorScheme="primary"
paddingSize="mediumSquare"
Expand All @@ -114,18 +135,19 @@ const DonateButton = (props: Clickable) => (
</Button.Root>
);

const ReportButton = (props: Clickable) => (
<Button.Root
onClick={props.onClick}
tooltipText="Report"
colorScheme="primary"
paddingSize="mediumSquare"
>
<Button.ButtonIcon>
<FontAwesomeIcon icon={faFlag} />
</Button.ButtonIcon>
</Button.Root>
);
// TODO: Enable and finish, when we have endpoint for submitting
// const ReportButton = (props: Clickable) => (
// <Button.Root
// onClick={props.onClick}
// tooltipText="Report"
// colorScheme="primary"
// paddingSize="mediumSquare"
// >
// <Button.ButtonIcon>
// <FontAwesomeIcon icon={faFlag} />
// </Button.ButtonIcon>
// </Button.Root>
// );

const DownloadButton = () => (
<Button.Root plain colorScheme="primary" paddingSize="medium">
Expand Down
1 change: 1 addition & 0 deletions apps/cyberstorm-nextjs/dapper/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function ClientDapper(props: React.PropsWithChildren) {
const config = {
apiHost: process.env.NEXT_PUBLIC_API_DOMAIN || "https://thunderstore.io",
sessionId: getCookie("sessionid"),
csrfToken: getCookie("csrftoken"),
};
const dapperConstructor = () => new DapperTs(config);

Expand Down
44 changes: 44 additions & 0 deletions packages/cyberstorm-forms/src/actions/PackageLikeAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { useFormToaster } from "@thunderstore/cyberstorm-forms";
import {
ApiAction,
packageLikeActionSchema,
} from "@thunderstore/ts-api-react-actions";
import { packageLike } from "@thunderstore/thunderstore-api";

export function PackageLikeAction(props: {
packageName: string;
uuid4: string;
isLiked: boolean;
currentUserUpdateTrigger: () => Promise<void>;
}) {
const { onSubmitSuccess, onSubmitError } = useFormToaster({
successMessage: `${props.isLiked ? "Unliked" : "Liked"} package ${
props.packageName
}`,
});

function onActionSuccess() {
props.currentUserUpdateTrigger();
onSubmitSuccess();
}

function onActionError() {
onSubmitError();
}

const onSubmit = ApiAction({
schema: packageLikeActionSchema,
meta: { uuid4: props.uuid4 },
endpoint: packageLike,
onSubmitSuccess: onActionSuccess,
onSubmitError: onActionError,
});

return function () {
onSubmit({ target_state: props.isLiked ? "unrated" : "rated" });
};
}

PackageLikeAction.displayName = "PackageLikeAction";
1 change: 1 addition & 0 deletions packages/cyberstorm-forms/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { useFormToaster } from "./useFormToaster";
export { TeamMemberChangeRoleAction } from "./actions/TeamMemberChangeRoleAction";
export { PackageLikeAction } from "./actions/PackageLikeAction";
export { FormSubmitButton } from "./components/FormSubmitButton";
export { FormSelectSearch } from "./components/FormSelectSearch";
export { FormMultiSelectSearch } from "./components/FormMultiSelectSearch";
Expand Down
10 changes: 10 additions & 0 deletions packages/cyberstorm/src/components/Button/Button.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@
--bg-color: #27275d;
}

.button__likeBlue {
background: #0b4162;

--button-border-color: #1ca3f5;
}

.button__likeBlue:hover {
background: #083149;
}

/* Button padding sizes */

.padding__none {
Expand Down
2 changes: 2 additions & 0 deletions packages/cyberstorm/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface ButtonProps {
| "overwolf"
| "specialGreen"
| "specialPurple"
| "likeBlue"
| "transparentDanger"
| "transparentDefault"
| "transparentTertiary"
Expand Down Expand Up @@ -181,6 +182,7 @@ const getStyle = (scheme: string) => {
overwolf: styles.button__overwolf,
specialGreen: styles.button__specialGreen,
specialPurple: styles.button__specialPurple,
likeBlue: styles.button__likeBlue,
transparentDanger: styles.button__transparentDanger,
transparentDefault: styles.button__transparentDefault,
transparentTertiary: styles.button__transparentTertiary,
Expand Down
1 change: 1 addition & 0 deletions packages/dapper-fake/src/fakers/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const getFakePackageListingDetails = async (
return {
...getFakePackageListing(community, namespace, name),

uuid4: faker.string.uuid(),
community_name: faker.word.sample(),
datetime_created: faker.date.past({ years: 2 }).toISOString(),
dependant_count: faker.number.int({ min: 0, max: 2000 }),
Expand Down
1 change: 1 addition & 0 deletions packages/dapper-ts/src/methods/packageListings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const dependencyShema = z.object({
});

const packageListingDetailSchema = packageListingSchema.extend({
uuid4: z.string().nonempty(),
community_name: z.string().nonempty(),
datetime_created: z.string().datetime(),
dependant_count: z.number().int().gte(0),
Expand Down
1 change: 1 addition & 0 deletions packages/dapper/src/types/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface PackageListing {
export type PackageListings = PaginatedList<PackageListing>;

export interface PackageListingDetails extends PackageListing {
uuid4: string;
community_name: string;
datetime_created: string;
dependant_count: number;
Expand Down
13 changes: 11 additions & 2 deletions packages/thunderstore-api/src/apiFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,29 @@ export async function apiFetch2(args: apiFetchArgs) {
return response.json();
}

export function apiFetch(config: RequestConfig, path: string, query?: string) {
export function apiFetch(
config: RequestConfig,
path: string,
query?: string,
request?: Omit<RequestInit, "headers">
) {
// TODO: Update the apiFetch signature to take in object args instead
// of positional arguments and then merge apiFetch and apiFetch2
// together. Someone else's job for now.
return apiFetch2({
config,
path,
query,
request,
});
}

function getAuthHeaders(config: RequestConfig): RequestInit["headers"] {
return config.sessionId
? { Authorization: `Session ${config.sessionId}` }
? {
Authorization: `Session ${config.sessionId}`,
"X-Csrftoken": config.csrfToken ? config.csrfToken : "",
}
: {};
}

Expand Down
3 changes: 2 additions & 1 deletion packages/thunderstore-api/src/fetch/currentUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { apiFetch } from "../apiFetch";

export async function fetchCurrentUser(config: RequestConfig) {
const path = "api/experimental/current-user/";
const request = { cache: "no-store" as RequestCache };

return await apiFetch(config, path);
return await apiFetch(config, path, undefined, request);
}
28 changes: 28 additions & 0 deletions packages/thunderstore-api/src/fetch/packageLike.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { RequestConfig } from "../index";
import { apiFetch2 } from "../apiFetch";

export type packageLikeMetaArgs = {
uuid4: string;
};

export type packageLikeApiArgs = {
target_state: "rated" | "unrated";
};

export function packageLike(
config: RequestConfig,
data: packageLikeApiArgs,
meta: packageLikeMetaArgs
) {
const path = `/api/v1/package/${meta.uuid4}/rate/`;

return apiFetch2({
config,
path,
request: {
method: "POST",
cache: "no-store",
body: JSON.stringify(data),
},
});
}
2 changes: 2 additions & 0 deletions packages/thunderstore-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface RequestConfig {
// TODO: This should not be explicitly bound to a session ID but rather just
// accept any authorization header. Noting as currently out of scope.
sessionId?: string;
csrfToken?: string;
}

export * from "./fetch/community";
Expand All @@ -28,5 +29,6 @@ export * from "./fetch/teamServiceAccountRemove";
export * from "./fetch/userDelete";
export * from "./fetch/teamDisbandTeam";
export * from "./fetch/teamEditMember";
export * from "./fetch/packageLike";
export * from "./fetch/teamRemoveMember";
export * from "./errors";
1 change: 1 addition & 0 deletions packages/ts-api-react-actions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { ApiAction } from "./ApiAction";
export { packageLikeActionSchema } from "./schema";
5 changes: 5 additions & 0 deletions packages/ts-api-react-actions/src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { z } from "zod";

export const packageLikeActionSchema = z.object({
target_state: z.union([z.literal("rated"), z.literal("unrated")]),
});
Loading
Loading