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

Add basket UI stuff (and a bunch of scaffolding) #10

Merged
merged 24 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9566456
Updates to session determination; starting out backporting the cart c…
jkachel Dec 5, 2024
1935ffa
Stop using window.location for the system slug parser, build a real b…
jkachel Dec 5, 2024
df169a5
Got more styling in place for the cart page and chooser stuff
jkachel Dec 6, 2024
5b71cd5
More migrating components, filling out the Order Summary and cart ite…
jkachel Dec 9, 2024
1bb9d1b
Fix some styling issues with the summary container
jkachel Dec 10, 2024
d5ecc7e
Sort-of finalizing the summary container - we need a Button that isn'…
jkachel Dec 10, 2024
c99e6ae
Fix styling on the cart item cards
jkachel Dec 10, 2024
20c2160
Cleanup and stuff for the cart display page
jkachel Dec 10, 2024
5e08cff
Client update, add discount functionality
jkachel Dec 11, 2024
f53267c
adding error reporting for the discount code
jkachel Dec 11, 2024
0dfb5d7
Pull Place Order out into its own component; add the actual checkout …
jkachel Dec 11, 2024
ea04888
Fixing formatting
jkachel Dec 12, 2024
053d9e3
Fixing linting errors
jkachel Dec 12, 2024
a01e7ba
Removing stuff and updating the docs a bit
jkachel Dec 12, 2024
4f6c1af
Fix some test errors, add a test for the getCurrentSystem, fix Jest s…
jkachel Dec 12, 2024
e1c1d9c
fix formatting
jkachel Dec 12, 2024
c9e9568
Cleanup stuff from comments
jkachel Dec 16, 2024
dd61ee7
Fixing typing on this too
jkachel Dec 16, 2024
a9eca4b
Updating querykey invalidation to include the changed data
jkachel Dec 16, 2024
d5259d3
removing stray console.log
jkachel Dec 16, 2024
3dfeb53
fix formatting
jkachel Dec 16, 2024
6c53d85
Swapping the CartPageContainer for generic Container
jkachel Dec 16, 2024
68c1043
Updating things from comments on the PR
jkachel Dec 17, 2024
4945a6d
rebase from main, update yarn.lock
jkachel Dec 17, 2024
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ First, ensure that you have the [Unified Ecommerce Bakend](https://github.com/mi

Environment variables are described in detail in `env/env.defaults.public`; all env vars should have functional defaults. However, a few dependencies to note:

- In the Unified Ecommerce backend, `MITOL_UE_PAYMENT_BASKET_ROOT` and `MITOL_UE_PAYMENT_BASKET_CHOOSER` should point to the this repo's frontend. (e.g., `https://uefe.odl.local:8072`)
- In the Unified Ecommerce backend, `MITOL_UE_PAYMENT_BASKET_ROOT` and `MITOL_UE_PAYMENT_BASKET_CHOOSER` should point to the this repo's frontend. (e.g., `https://ue.odl.local:8072`)

### Run the app

#### With Docker

With `docker compose up`, you should be up and running. Visit the application at http://uefe.odl.local:8072
With `docker compose up`, you should be up and running. Visit the application at http://ue.odl.local:8072

#### Without Docker

Expand All @@ -25,8 +25,10 @@ You can run the app outside of docker. This may be faster and more convenient. T
1. Some way to load environment variables. [direnv](https://direnv.net/) is a great tool for this; a sample `.envrc` file is committed in the repo.
2. A NodeJS runtime; [`nvm`](https://github.com/nvm-sh/nvm) is a simple tool for managing NodeJS versions.

With that done, `yarn start`, `yarn install`, and visit http://uefe.odl.local:8072
With that done, `yarn start`, `yarn install`, and visit http://ue.odl.local:8072

## Accessing the Application

The Unified Ecommerce backend uses same-site cookies for authentication. Therefore, the frontend client must run on the "same site" as the backend. Therefore, if the backend runs on `ue.odl.local:8073`, you **must** access the frontend on at a hostname such as `uefe.odl.local` (or `*.odl.local`), _not_ `localhost`.
The Unified Ecommerce backend uses same-site cookies for authentication. Therefore, the frontend client must run on the "same site" as the backend. Therefore, if the backend runs on `ue.odl.local:8073`, you **must** access the frontend on at a hostname such as `ue.odl.local` (or `*.odl.local`), _not_ `localhost`.

To prevent CORS and CSRF errors, set the frontend and backend URLs to be either the same hostname (`ue.odl.local`) or set the backend to be a subdomain of the frontend (`api.ue.odl.local` and `ue.odl.local` - this is closer to the actual deployment).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@faker-js/faker": "^9.0.0",
"@mitodl/smoot-design": "^1.0.1",
"@mitodl/smoot-design": "^1.1.1",
"@mui/material": "^6.1.8",
"@mui/material-nextjs": "^6.1.8",
"@remixicon/react": "^4.2.0",
Expand Down
25 changes: 0 additions & 25 deletions src/app/cart/page.tsx

This file was deleted.

9 changes: 0 additions & 9 deletions src/app/checkout/page.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import Header from "@/page-components/Header/Header";
import Header from "@/components/Header/Header";
import EnsureSession from "@/page-components/EnsureSession/EnsureSession";
import "./global.css";
import Providers from "./providers";
Expand Down
157 changes: 154 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,159 @@
"use client";
import React from "react";
import { useSearchParams, useRouter } from "next/navigation";
import { useMetaIntegratedSystemsList } from "@/services/ecommerce/meta/hooks";
import { styled } from "@mitodl/smoot-design";
import { Typography } from "@mui/material";
import Container from "@mui/material/Container";
import { getCurrentSystem } from "@/utils/system";
import { Card } from "@/components/Card/Card";
import CartItem from "@/page-components/CartItem/CartItem";
import CartSummary from "@/page-components/CartSummary/CartSummary";
import StyledCard from "@/components/Card/StyledCard";
import { UseQueryResult } from "@tanstack/react-query";
import type {
PaginatedBasketWithProductList,
BasketWithProduct,
} from "@/services/ecommerce/generated/v0";

import {
usePaymentsBasketList,
usePaymentsBasketRetrieve,
} from "@/services/ecommerce/payments/hooks";
import {
BasketItemWithProduct,
IntegratedSystem,
} from "@/services/ecommerce/generated/v0";

type CartProps = {
system: string;
};

type CartBodyProps = {
systemId: number;
};

const SelectSystemContainer = styled.div`
margin: 16px 0;
`;

const SelectSystem: React.FC = () => {
const systems = useMetaIntegratedSystemsList();
const router = useRouter();

const hndSystemChange = (ev: React.ChangeEvent<HTMLSelectElement>) => {
router.push(`/?system=${ev.target.value}`);
};

return (
<SelectSystemContainer>
{systems.isFetched && systems.data ? (
<>
<label htmlFor="system">Select a system:</label>
<select name="system" id="system" onChange={hndSystemChange}>
<option value="">Select a system</option>
{systems.data.results.map((system) => (
<option key={system.id} value={system.slug || ""}>
{system.name}
</option>
))}
</select>
</>
) : (
<p>Loading systems...</p>
)}
</SelectSystemContainer>
);
};

const CartContainer = styled.div``;

const CartBodyContainer = styled.div`
width: 100%;
display: flex;
gap: 48px;
align-items: start;
`;

const CartHeader = styled.div`
width: 100%;
flex-grow: 1;
margin-bottom: 16px;
`;

const CartItemsContainer = styled.div`
width: auto;
flex-grow: 1;
`;

const CartBody: React.FC<CartBodyProps> = ({ systemId }) => {
const basket = usePaymentsBasketList({
integrated_system: systemId,
}) as UseQueryResult<PaginatedBasketWithProductList>;
const basketDetails = usePaymentsBasketRetrieve(
basket.data?.results[0]?.id || 0,
{ enabled: !!basket.data?.count },
) as UseQueryResult<BasketWithProduct>;

return basketDetails.isFetched &&
basketDetails?.data?.basket_items &&
basketDetails.data.basket_items.length > 0 ? (
<CartBodyContainer>
<CartItemsContainer>
{basketDetails.data.basket_items.map((item: BasketItemWithProduct) => (
<CartItem item={item} key={`ue-basket-item-${item.id}`} />
))}
</CartItemsContainer>
<CartSummary cartId={basketDetails.data.id} />
</CartBodyContainer>
) : (
<CartBodyContainer>
<StyledCard>
<Card.Content>
<p>Your cart is empty.</p>
</Card.Content>
</StyledCard>
</CartBodyContainer>
);
};

const Cart: React.FC<CartProps> = ({ system }) => {
const systems = useMetaIntegratedSystemsList();
const selectedSystem = systems.data?.results.find(
(integratedSystem: IntegratedSystem) => integratedSystem.slug === system,
);

return (
selectedSystem && (
<CartContainer>
<CartHeader>
<Typography variant="h3">
You are about to purchase the following:
</Typography>
</CartHeader>
{selectedSystem && <CartBody systemId={selectedSystem.id} />}
</CartContainer>
)
);
};

const Home = () => {
const searchParams = useSearchParams();
const specifiedSystem = getCurrentSystem(searchParams);

return (
<div>
<h1>Home</h1>
</div>
<Container>
{specifiedSystem === "" && (
<StyledCard>
<Card.Content>
We can't determine what system you're trying to access. Please
choose a system to continue.
<SelectSystem />
</Card.Content>
</StyledCard>
)}
{specifiedSystem !== "" && <Cart system={specifiedSystem} />}
</Container>
);
};

Expand Down
31 changes: 31 additions & 0 deletions src/components/Card/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render } from "@testing-library/react";
import { Card } from "./Card";

describe("Card", () => {
test("has class MitCard-root on root element", () => {
const { container } = render(
<Card className="Foo">
<Card.Title>Title</Card.Title>
<Card.Image src="https://via.placeholder.com/150" alt="placeholder" />
<Card.Info>Info</Card.Info>
<Card.Footer>Footer</Card.Footer>
<Card.Actions>Actions</Card.Actions>
</Card>,
);
const card = container.firstChild as HTMLElement;
const title = card.querySelector(".MitCard-title");
const image = card.querySelector(".MitCard-image");
const info = card.querySelector(".MitCard-info");
const footer = card.querySelector(".MitCard-footer");
const actions = card.querySelector(".MitCard-actions");

expect(card).toHaveClass("MitCard-root");
expect(card).toHaveClass("Foo");
expect(title).toHaveTextContent("Title");
expect(image).toHaveAttribute("src", "https://via.placeholder.com/150");
expect(image).toHaveAttribute("alt", "placeholder");
expect(info).toHaveTextContent("Info");
expect(footer).toHaveTextContent("Footer");
expect(actions).toHaveTextContent("Actions");
});
});
Loading
Loading