Skip to content

Commit

Permalink
Offer pages, #21, Cache with handler
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiancook committed Jun 19, 2023
1 parent 61a9a3b commit 4138226
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ export interface ShipmentLocation {
locationId?: string;
inventoryId?: string;
inventoryItemId?: string;
orderId?: string;
address?: string[]; // Human-readable address
countryCode?: string;
}
Expand Down
20 changes: 10 additions & 10 deletions src/package.readonly.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// File generated by scripts/pre-build.js

export const commit = "7c05006eecc92071440f4abf173011ea48340506";
export const commitShort = "7c05006";
export const commit = "61a9a3bd284fb6ef24a4899710898aa35b4eac9e";
export const commitShort = "61a9a3b";
export const commitAuthor = "Fabian Cook";
export const commitEmail = "[email protected]";
export const commitMessage = "Payment & PaymentMethod scenarios, #15, #21";
export const commitAt = "2023-06-18T23:51:35.000Z";
export const secondsBetweenCommitAndBuild = 4327.58;
export const minutesBetweenCommitAndBuild = 72.13;
export const timeBetweenCommitAndBuild = "72 minutes and 7 seconds";
export const commitMessage = "ShipmentLocation schema";
export const commitAt = "2023-06-19T01:12:27.000Z";
export const secondsBetweenCommitAndBuild = 83.97;
export const minutesBetweenCommitAndBuild = 1.4;
export const timeBetweenCommitAndBuild = "1 minutes and 23 seconds";
// Variables to be replaced after tests
export const secondsBetweenCommitAndTestCompletion = "4339.11";
export const minutesBetweenCommitAndTestCompletion = "72.32";
export const timeBetweenCommitAndTestCompletion = "72 minutes and 19 seconds";
export const secondsBetweenCommitAndTestCompletion = "";
export const minutesBetweenCommitAndTestCompletion = "";
export const timeBetweenCommitAndTestCompletion = "";
24 changes: 23 additions & 1 deletion src/react/server/data/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
Partner,
Organisation,
User,
Product,
Product, Offer,
} from "../../../data";
import {createContext, ProviderProps, useContext, useMemo} from "react";
import { ok } from "../../../is";
Expand All @@ -30,6 +30,7 @@ export interface ReactData {
user?: User;
roles?: AuthenticationRole[];
products?: Product[];
offers?: Offer[];
}

export const DataContext = createContext<ReactData | undefined>(undefined);
Expand Down Expand Up @@ -189,4 +190,25 @@ export function useProducts() {
export function useConfig(): Config {
const { config } = useData();
return config || {}
}

export function useOffers() {
const { offers } = useData();
return useMemo(() => offers || [], [offers]);
}

export function useProduct(productId?: string): Product | undefined {
const products = useProducts();
return useMemo(() => {
if (!productId) return undefined;
return products.find((product) => product.productId === productId);
}, [products, productId]);
}

export function useOffer(offerId?: string): Offer | undefined {
const offers = useOffers();
return useMemo(() => {
if (!offerId) return undefined;
return offers.find((offer) => offer.offerId === offerId);
}, [offers, offerId]);
}
5 changes: 4 additions & 1 deletion src/react/server/paths/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as product from "./product"
import * as invite from "./invite"
import * as offer from "./offer"
import {View} from "../../../view";

import * as PartnersView from "./partners";
Expand Down Expand Up @@ -31,5 +32,7 @@ export const views: View[] = [
product.create,
product.list,
invite.create,
invite.accept
invite.accept,
offer.create,
offer.list
];
170 changes: 170 additions & 0 deletions src/react/server/paths/offer/create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { FastifyRequest } from "fastify";
import {
useError,
useMaybeBody,
useMaybeResult,
useProduct,
useQuery,
useQuerySearch,
useSubmitted,
useTimezone
} from "../../data";
import {
Offer,
OfferData,
addOffer
} from "../../../../data";
import {ok} from "../../../../is";

export const path = "/offer/create";

export const MINUTE_MS = 60 * 1000;
export const DAY_MS = 24 * 60 * MINUTE_MS;

const FORM_CLASS = `
mt-1
block
w-full
md:max-w-sm
rounded-md
border-gray-300
shadow-sm
focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
disabled:bg-slate-300 disabled:cursor-not-allowed
`.trim();
const FORM_GROUP_CLASS = `block py-2`;

function assertOfferData(value: unknown): asserts value is OfferData {
ok<OfferData>(value);
ok(value.currency, "Expected currency");
}

export async function submit(request: FastifyRequest) {
const data = request.body;
assertOfferData(data);
const offer = await addOffer(data);
console.log({ offer });
return { success: true, offer };
}

const LINK_CLASS = "text-blue-600 hover:bg-white underline hover:underline-offset-2";

export function CreateOffer() {
const query = useQuery<{ productId?: string }>();
const body = useMaybeBody<OfferData>();
const queryProduct = useProduct(query.productId ?? body?.items[0]?.productId);
const timezone = useTimezone();
const submitted = useSubmitted();
const result = useMaybeResult<{ success: boolean; offer: Offer }>();
const error = useError();

console.error(error);

return <OfferBody body={result?.success ? undefined : body} />

function OfferBody({ body }: { body?: OfferData }) {
return (
<form name="offer" action="/offer/create#action-section" method="post">
{
queryProduct ? (
<>
<input type="hidden" name="items[0].type" value="product" />
<input type="hidden" name="items[0].productId" value={queryProduct.productId} />
<div className="flex flex-col">
<label className={FORM_GROUP_CLASS}>
<span className="text-gray-700">Product Name</span>
<input
className={FORM_CLASS}
disabled
type="text"
placeholder="Product Name"
defaultValue={queryProduct.productName || ""}
/>
</label>
</div>
<div className="flex flex-col">
<label className={FORM_GROUP_CLASS}>
<span className="text-gray-700">Product Quantity</span>
<input
className={FORM_CLASS}
type="text"
placeholder="Product Quantity"
defaultValue={(body?.items[0]?.quantity ?? "1").toString()}
/>
</label>
</div>
</>
) : undefined
}
<div className="flex flex-col">
<label className={FORM_GROUP_CLASS}>
<span className="text-gray-700">Offer Name</span>
<input
className={FORM_CLASS}
type="text"
name="offerName"
placeholder="Offer Name"
defaultValue={body?.offerName || ""}
/>
</label>
</div>
<div className="flex flex-col">
<label className={FORM_GROUP_CLASS}>
<span className="text-gray-700">Price</span>
<input
className={FORM_CLASS}
type="text"
name="price"
placeholder="Price"
defaultValue={body?.price || ""}
/>
</label>
</div>
<div className="flex flex-col">
<label className={FORM_GROUP_CLASS}>
<span className="text-gray-700">Currency</span>
<select
className={FORM_CLASS}
name="currency"
defaultValue={body?.currency || "NZD"}
>
<option value="NZD">New Zealand Dollar</option>
</select>
</label>
</div>
<label htmlFor="public" className="my-4 flex flex-row align-start">
<input
name="public"
id="public"
type="checkbox"
className="form-checkbox rounded m-1"
defaultChecked={false}
/>
<span className="flex flex-col ml-4">
Should the offer be visible to the public?
</span>
</label>
<style dangerouslySetInnerHTML={{ __html: `
.non-generic-organisation-name {
display: none;
}
label:has(input:checked) + .non-generic-organisation-name {
display: flex;
}
`.trim()}} />
<div id="action-section">
<button
type="submit"
className="bg-sky-500 hover:bg-sky-700 px-4 py-2.5 text-sm leading-5 rounded-md font-semibold text-white"
>
Save Offer
</button>
</div>
</form>
)
}
}

export const Component = CreateOffer;
2 changes: 2 additions & 0 deletions src/react/server/paths/offer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as create from "./create";
export * as list from "./list";
42 changes: 42 additions & 0 deletions src/react/server/paths/offer/list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {useData, useOffers, useProduct, useQuery} from "../../data";
import {listOffers} from "../../../../data";
import {isAnonymous} from "../../../../authentication";

export const path = "/offers";
export const anonymous = true;
export const cached = true;

const LINK_CLASS = "text-blue-600 hover:bg-white underline hover:underline-offset-2";

export async function handler() {
return {
offers: await listOffers({
public: isAnonymous()
})
}
}

export function ListOffers() {
const query = useQuery<{ productId?: string }>();
const offers = useOffers();
const { isAnonymous } = useData();
const queryProduct = useProduct(query.productId);
let createUrl = "/offer/create";
if (queryProduct) {
createUrl = `${createUrl}?productId=${queryProduct.productId}`
}
return (
<div className="flex flex-col">
{!isAnonymous ? <a href={createUrl} className={LINK_CLASS}>Create Offer{queryProduct.productId ? ` for ${queryProduct.productName}` : ""}</a> : undefined}
<div className="flex flex-col divide-y">
{offers.map(offer => (
<div key={offer.offerId}>
{offer.offerName}
</div>
))}
</div>
</div>
)
}

export const Component = ListOffers;
4 changes: 2 additions & 2 deletions src/react/server/paths/product/create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function CreateProduct() {

function ProductBody({ body }: { body?: ProductData }) {
return (
<form name="happening" action="/product/create#action-section" method="post">
<form name="product" action="/product/create#action-section" method="post">
<div className="flex flex-col">
<label className={FORM_GROUP_CLASS}>
<span className="text-gray-700">Product Name</span>
Expand All @@ -73,7 +73,7 @@ export function CreateProduct() {
defaultChecked={false}
/>
<span className="flex flex-col ml-4">
Should the public be visible to the public?
Should the product be visible to the public?
</span>
</label>
<style dangerouslySetInnerHTML={{ __html: `
Expand Down
17 changes: 14 additions & 3 deletions src/react/server/paths/product/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,27 @@ export const path = "/products";
export const anonymous = true;
export const cache = true;

const LINK_CLASS = "text-blue-600 hover:bg-white underline hover:underline-offset-2";

export function ListProducts() {
const products = useProducts();
const { isAnonymous } = useData();
return (
<div className="flex flex-col">
{!isAnonymous ? <a href="/product/create">Create Product</a> : undefined}
{!isAnonymous ? <a href="/product/create" className={LINK_CLASS}>Create Product</a> : undefined}
<div className="flex flex-col divide-y">
{products.map(product => (
<div key={product.productId}>
{product.productName}
<div key={product.productId} className="flex flex-row justify-between">
<div>{product.productName}</div>
{
!isAnonymous ? (
<div>
<a href={`/offers?productId=${product.productId}`} className={LINK_CLASS}>
Offers
</a>
</div>
) : undefined
}
</div>
))}
</div>
Expand Down
Loading

0 comments on commit 4138226

Please sign in to comment.