Skip to content

Commit

Permalink
Merge pull request #856 from funmusicplace/feat-merch
Browse files Browse the repository at this point in the history
Feat merch
  • Loading branch information
simonv3 authored Sep 18, 2024
2 parents 55a6a60 + 688b750 commit b7a1479
Show file tree
Hide file tree
Showing 43 changed files with 1,280 additions and 199 deletions.
2 changes: 1 addition & 1 deletion client/src/components/Artist/ArtistSupportBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const ArtistSupportBox: React.FC<{
/>
</>
)}
{(isSubscribedToTier || ownedByUser || isSubscribedToArtist) && (
{(isSubscribedToTier || isSubscribedToArtist) && (
<Box
className={css`
text-align: center;
Expand Down
113 changes: 113 additions & 0 deletions client/src/components/FulFillment/CustomerPopUp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { css } from "@emotion/css";
import styled from "@emotion/styled";
import Button from "components/common/Button";
import Modal from "components/common/Modal";
import { SelectEl } from "components/common/Select";
import { formatDate } from "components/TrackGroup/ReleaseDate";
import React from "react";
import { useTranslation } from "react-i18next";
import api from "services/api";

const Underline = styled.div`
border-bottom: 1px solid var(--mi-darken-x-background-color);
padding-bottom: 1rem;
margin-bottom: 1rem;
`;

const Section = styled.div`
display: flex;
margin-bottom: 0.5rem;
label {
width: calc(3 / 12 * 100%) !important;
font-weight: bold;
}
`;

const CustomerPopUp: React.FC<{ purchase: MerchPurchase }> = ({ purchase }) => {
const { i18n, t } = useTranslation("translation", {
keyPrefix: "fulfillment",
});

const [status, setStatus] = React.useState(purchase.fulfillmentStatus);

const updateStatus = React.useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
e.preventDefault();
e.stopPropagation();
try {
api.put(`manage/purchases/${purchase.id}`, {
fulfillmentStatus: e.target.value,
});
setStatus(e.target.value as MerchPurchase["fulfillmentStatus"]);
} catch (e) {}
},
[purchase.id]
);
return (
<div>
<Underline>
<h2>{purchase.user.name}</h2>
<Section>
<label>{t("itemPurchased")}</label>{" "}
<span>{purchase.merch.title}</span>
</Section>
<Section>
<label>{t("quantity")}</label> <span>{purchase.quantity}</span>
</Section>
<Section>
<label>{t("orderDate")}</label>
{formatDate({
date: purchase.updatedAt,
i18n,
options: { dateStyle: "short" },
})}
</Section>
</Underline>
<Underline>
<Section>
<label>{t("shippingAddress")}</label>
<p>
{purchase.user.name} <br />
{purchase.shippingAddress.line1 && (
<>
{purchase.shippingAddress.line1}
<br />
</>
)}
{purchase.shippingAddress.line1 && (
<>
{purchase.shippingAddress.line1}
<br />
</>
)}
{purchase.shippingAddress.city && (
<>
{purchase.shippingAddress.city}
<br />
</>
)}
{purchase.shippingAddress.state},
{purchase.shippingAddress.postal_code}
</p>
</Section>
</Underline>
<Underline>
<Section>
<label>{t("fulfillmentStatus")}</label>
<div>
<p></p>
<SelectEl value={status} onChange={updateStatus}>
<option value="NO_PROGRESS">{t("noProgress")}</option>
<option value="STARTED">{t("started")}</option>
<option value="SHIPPED">{t("shipped")}</option>
<option value="COMPLETED">{t("completed")}</option>
</SelectEl>
</div>
</Section>
</Underline>
</div>
);
};

export default CustomerPopUp;
80 changes: 80 additions & 0 deletions client/src/components/FulFillment/Fulfillment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { css } from "@emotion/css";
import Table from "components/common/Table";
import React from "react";
import { useTranslation } from "react-i18next";
import api from "services/api";
import FulfillmentRow from "./FulfillmentRow";

export const Fulfillment: React.FC = () => {
const { t } = useTranslation("translation", {
keyPrefix: "fulfillment",
});
const [results, setResults] = React.useState<MerchPurchase[]>([]);
const [total, setTotal] = React.useState<number>();

const callback = React.useCallback(async (search?: URLSearchParams) => {
if (search) {
search.append("orderBy", "createdAt");
}
const { results, total: totalResults } = await api.getMany<MerchPurchase>(
`manage/purchases?${search?.toString()}`
);
setTotal(totalResults);
setResults(results);
}, []);

React.useEffect(() => {
callback();
}, [callback]);

console.log("results", results);

return (
<div
className={css`
flex-grow: 1;
padding: 1rem;
max-width: 100%;
`}
>
<h3>Orders & Fulfillment</h3>
<h4>Total results: {total}</h4>
{results.length > 0 && (
<div
className={css`
max-width: 100%;
overflow: scroll;
background-color: var(--mi-lighten-background-color);
`}
>
<Table>
<thead>
<tr>
<th />
<th>{t("artist")}</th>
<th>{t("merchItem")}</th>
<th>{t("customer")}</th>
<th>{t("email")}</th>
<th>{t("quantity")}</th>
<th>{t("fulfillmentStatus")}</th>
<th>{t("orderDate ")}</th>
<th>{t("lastUpdated")}</th>
</tr>
</thead>
<tbody>
{results.map((purchase, index) => (
<FulfillmentRow
key={purchase.id}
purchase={purchase}
index={index}
/>
))}
</tbody>
</Table>
</div>
)}
</div>
);
};

export default Fulfillment;
72 changes: 72 additions & 0 deletions client/src/components/FulFillment/FulfillmentRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Button from "components/common/Button";
import Modal from "components/common/Modal";
import { SelectEl } from "components/common/Select";
import { formatDate } from "components/TrackGroup/ReleaseDate";
import React from "react";
import { useTranslation } from "react-i18next";
import { FaEye } from "react-icons/fa";
import api from "services/api";
import CustomerPopUp from "./CustomerPopUp";
import { css } from "@emotion/css";

const statusMap = {
SHIPPED: "shipped",
NO_PROGRESS: "noProgress",
STARTED: "started",
COMPLETED: "completed",
};

const FulfillmentRow: React.FC<{ purchase: MerchPurchase; index: number }> = ({
purchase,
index,
}) => {
const { i18n, t } = useTranslation("translation", {
keyPrefix: "fulfillment",
});
const [isEditing, setIsEditing] = React.useState(false);

return (
<>
<tr
key={purchase.id}
onClick={() => setIsEditing(true)}
className={css`
cursor: pointer;
`}
>
<td>
<Button onClick={() => setIsEditing(true)} startIcon={<FaEye />} />
</td>
<td>{purchase.merch.artist.name}</td>
<td>{purchase.merch.title}</td>
<td>{purchase.user.name}</td>
<td>{purchase.user.email}</td>
<td>{purchase.quantity}</td>
<td>{t(statusMap[purchase.fulfillmentStatus])}</td>
<td>
{formatDate({
date: purchase.createdAt,
i18n,
options: { dateStyle: "short" },
})}
</td>
<td>
{formatDate({
date: purchase.updatedAt,
i18n,
options: { dateStyle: "short" },
})}
</td>
</tr>
<Modal
open={isEditing}
onClose={() => setIsEditing(false)}
title={t("purchaseDetails")}
>
<CustomerPopUp purchase={purchase} />
</Modal>
</>
);
};

export default FulfillmentRow;
12 changes: 12 additions & 0 deletions client/src/components/Header/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ const Menu: React.FC = (props) => {
</Button>
</li>
)}
{user?.isAdmin && (
<li>
<Button
onClick={() => {
setIsMenuOpen(false);
navigate("/fulfillment");
}}
>
{t("fulfillment")}
</Button>
</li>
)}
<li>
<Button
onClick={onLogOut}
Expand Down
96 changes: 96 additions & 0 deletions client/src/components/ManageArtist/Merch/DestinationListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { moneyDisplay } from "components/common/Money";
import { css } from "@emotion/css";
import React from "react";
import { useFormContext } from "react-hook-form";
import FormComponent from "components/common/FormComponent";
import { useTranslation } from "react-i18next";
import { SelectEl } from "components/common/Select";
import countryCodesCurrencies from "./country-codes-currencies";
import { InputEl } from "components/common/Input";

const DestinationListItem: React.FC<{
dest: Partial<ShippingDestination>;
index: number;
isEditing: boolean;
}> = ({ dest, isEditing, index }) => {
const { t } = useTranslation("translation", { keyPrefix: "manageMerch" });
const methods = useFormContext();

const allDestinations = methods.watch("destinations");
const onlyOneDestination = allDestinations.length === 1;
const defaultOption = onlyOneDestination
? t("everywhere")
: t("everywhereElse");
const destinationString = dest.destinationCountry
? dest.destinationCountry
: defaultOption;

return (
<li>
{isEditing && (
<div
className={css`
width: 100%;
> div {
max-width: 45%;
display: inline-block;
margin-right: 1rem;
}
`}
>
<FormComponent>
<label>{t("destinationCountry")}</label>
<SelectEl
{...methods.register(`destinations.${index}.destinationCountry`)}
>
<option value="">{defaultOption}</option>
{countryCodesCurrencies.map((country) => (
<option key={country.countryCode} value={country.countryCode}>
{country.countryName} {country.countryCode}
</option>
))}
</SelectEl>
</FormComponent>
<FormComponent>
<label>{t("costUnit")}</label>
<InputEl
type="number"
{...methods.register(`destinations.${index}.costUnit`)}
/>
</FormComponent>
<FormComponent>
<label>{t("costExtraUnit")}</label>
<InputEl
type="number"
{...methods.register(`destinations.${index}.costExtraUnit`)}
/>
</FormComponent>
</div>
)}
{!isEditing && (
<>
<em>
{dest.homeCountry} -&gt; {destinationString}
</em>
{dest.currency && (
<>
{moneyDisplay({
amount: dest.costUnit,
currency: dest.currency,
})}{" "}
(
{moneyDisplay({
amount: dest.costExtraUnit,
currency: dest.currency,
})}
)
</>
)}
</>
)}
</li>
);
};

export default DestinationListItem;
Loading

0 comments on commit b7a1479

Please sign in to comment.