Compare commits

...

3 Commits

Author SHA1 Message Date
Trisha Lim
5b042c801b fix cotext field 2025-06-09 14:56:00 +01:00
Trisha Lim
d274e71aab fix reactivity 2025-06-09 14:53:39 +01:00
Trisha Lim
22c05e245f useForm hook 2025-06-09 14:30:41 +01:00
6 changed files with 188 additions and 5 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Form example</title>
<link rel="icon" type="image/png" href="./public/favicon.ico">
<link rel="icon" type="image/png" href="./favicon.ico">
</head>
<body class="h-full flex flex-col bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<div id="root" class="align-self-center flex-1"></div>

View File

@@ -14,6 +14,7 @@
"hash-slash": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"jazz-inspector": "workspace:*",
"react": "19.0.0",
"react-dom": "19.0.0"
},

View File

@@ -1,11 +1,13 @@
import { useCoState } from "jazz-react";
import { LinkToHome } from "./LinkToHome.tsx";
import { OrderForm } from "./OrderForm.tsx";
import { OrderFormWithSaveButton } from "./OrderFormWithSaveButton.tsx";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { BubbleTeaOrder } from "./schema.ts";
export function EditOrder(props: { id: string }) {
const order = useCoState(BubbleTeaOrder, props.id);
const order = useCoState(BubbleTeaOrder, props.id, {
resolve: { addOns: true, instructions: true },
});
if (!order) return;
@@ -13,13 +15,17 @@ export function EditOrder(props: { id: string }) {
<>
<LinkToHome />
<OrderThumbnail order={order} />
<div>
<p>Saved order:</p>
<OrderThumbnail order={order} />
</div>
<h1 className="text-lg">
<strong>Edit your bubble tea order 🧋</strong>
</h1>
<OrderForm order={order} />
<OrderFormWithSaveButton order={order} />
</>
);
}

View File

@@ -0,0 +1,171 @@
import { useCoState } from "jazz-react";
import { CoPlainText, Loaded } from "jazz-tools";
import { useEffect, useState } from "react";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import {
BubbleTeaAddOnTypes,
BubbleTeaBaseTeaTypes,
BubbleTeaOrder,
ListOfBubbleTeaAddOns,
} from "./schema.ts";
type LoadedBubbleTeaOrder = Loaded<
typeof BubbleTeaOrder,
{ addOns: { $each: true }; instructions: true }
>;
const useOrderForm = (order: LoadedBubbleTeaOrder) => {
const [id, setId] = useState<string | undefined>();
const value = useCoState(BubbleTeaOrder, id, {
resolve: { addOns: { $each: true }, instructions: true },
});
useEffect(() => {
// deep clone order
const cloned = BubbleTeaOrder.create({
...order,
instructions: CoPlainText.create(`${order.instructions}` || ""),
addOns: ListOfBubbleTeaAddOns.create([...order.addOns]),
});
setId(cloned.id);
}, [order.id]);
if (!value) return { value, save: () => {} };
const save = () => {
order.baseTea = value.baseTea;
value.addOns.applyDiff(order.addOns);
order.deliveryDate = value.deliveryDate;
order.withMilk = value.withMilk;
const instructions = order.instructions || CoPlainText.create("");
if (value.instructions) {
instructions.applyDiff(value.instructions.toString());
order.instructions = instructions;
}
};
return { value, save };
};
export function OrderFormWithSaveButton({
order: originalOrder,
}: {
order: LoadedBubbleTeaOrder;
}) {
const { value: order, save } = useOrderForm(originalOrder);
if (!order) return null;
const handleInstructionsChange = (
e: React.ChangeEvent<HTMLTextAreaElement>,
) => {
if (order.instructions) {
return order.instructions.applyDiff(e.target.value);
}
order.instructions = CoPlainText.create(e.target.value, order._owner);
};
const submit = (e: React.FormEvent<HTMLFormElement>) => {
console.log("submit form");
e.preventDefault();
save();
};
return (
<form onSubmit={submit} className="grid gap-5">
<div>
<p>Unsaved order:</p>
<OrderThumbnail order={order} />
</div>
<div className="flex flex-col gap-2">
<label htmlFor="baseTea">Base tea</label>
<select
name="baseTea"
id="baseTea"
value={order.baseTea || ""}
className="dark:bg-transparent"
onChange={(e) => (order.baseTea = e.target.value as any)}
required
>
<option value="" disabled>
Please select your preferred base tea
</option>
{BubbleTeaBaseTeaTypes.map((teaType) => (
<option key={teaType} value={teaType}>
{teaType}
</option>
))}
</select>
</div>
<fieldset>
<legend className="mb-2">Add-ons</legend>
{BubbleTeaAddOnTypes.map((addOn) => (
<div key={addOn} className="flex items-center gap-2">
<input
type="checkbox"
value={addOn}
name={addOn}
id={addOn}
checked={order.addOns?.includes(addOn) || false}
onChange={(e) => {
if (e.target.checked) {
order.addOns?.push(addOn);
} else {
order.addOns?.splice(order.addOns?.indexOf(addOn), 1);
}
}}
/>
<label htmlFor={addOn}>{addOn}</label>
</div>
))}
</fieldset>
<div className="flex flex-col gap-2">
<label htmlFor="deliveryDate">Delivery date</label>
<input
type="date"
name="deliveryDate"
id="deliveryDate"
className="dark:bg-transparent"
value={order.deliveryDate?.toISOString().split("T")[0] || ""}
onChange={(e) => (order.deliveryDate = new Date(e.target.value))}
required
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
name="withMilk"
id="withMilk"
checked={order.withMilk}
onChange={(e) => (order.withMilk = e.target.checked)}
/>
<label htmlFor="withMilk">With milk?</label>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="instructions">Special instructions</label>
<textarea
name="instructions"
id="instructions"
value={`${order.instructions}`}
className="dark:bg-transparent"
onChange={handleInstructionsChange}
></textarea>
</div>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Submit
</button>
</form>
);
}

View File

@@ -3,6 +3,7 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { JazzInspector } from "jazz-inspector";
import { apiKey } from "./apiKey";
import { JazzAccount } from "./schema.ts";
@@ -15,6 +16,7 @@ createRoot(document.getElementById("root")!).render(
AccountSchema={JazzAccount}
>
<App />
<JazzInspector />
</JazzProvider>
</StrictMode>,
);

3
pnpm-lock.yaml generated
View File

@@ -827,6 +827,9 @@ importers:
hash-slash:
specifier: workspace:*
version: link:../../packages/hash-slash
jazz-inspector:
specifier: workspace:*
version: link:../../packages/jazz-inspector
jazz-react:
specifier: workspace:*
version: link:../../packages/jazz-react