metatrondelivery/app/routes/app.locations.new.tsx
metatroncubeswdev cc20f20fe3
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
fix: uncontrolled Polaris TextFields couldn't be typed into
app.locations.new.tsx's "Location name"/"Address" fields and
app.blackouts._index.tsx's "Date"/"Reason" fields were missing
value/onChange — Polaris TextField is fully controlled, so without that
wiring every keystroke gets overwritten back to an empty string on
re-render, making the field appear frozen. Every other form in the app
already followed the value/onChange + useState pattern; these four fields
were the only ones missed, and went uncaught until live browser testing
was actually possible tonight (shopify.web.toml was missing until now).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 02:23:22 -04:00

100 lines
3.3 KiB
TypeScript

import { useState } from "react";
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { Page, Card, BlockStack, Banner, FormLayout, TextField, Button } from "@shopify/polaris";
import { TitleBar } from "@shopify/app-bridge-react";
import { authenticate } from "../shopify.server";
import db from "../db.server";
import { canAddLocation, getShopTier, locationLimitFor } from "../services/billing.server";
export const action = async ({ request }: ActionFunctionArgs) => {
const { session } = await authenticate.admin(request);
const formData = await request.formData();
const name = String(formData.get("name") || "").trim();
const address = String(formData.get("address") || "").trim();
const timezone = String(formData.get("timezone") || "").trim();
const errors: Record<string, string> = {};
if (!name) errors.name = "Name is required";
if (!timezone) errors.timezone = "Timezone is required";
if (Object.keys(errors).length > 0) {
return { errors };
}
if (!(await canAddLocation(session.shop))) {
const tier = await getShopTier(session.shop);
return {
errors: {
plan: `Your ${tier} plan allows up to ${locationLimitFor(tier)} location(s). Upgrade to add more.`,
},
};
}
const location = await db.location.create({
data: { shopDomain: session.shop, name, address, timezone },
});
return redirect(`/app/locations/${location.id}`);
};
export default function NewLocation() {
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
const [name, setName] = useState("");
const [address, setAddress] = useState("");
const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone);
return (
<Page>
<TitleBar title="Add location" />
<Card>
<Form method="post">
<FormLayout>
{actionData?.errors?.plan && (
<Banner tone="warning" title="Location limit reached">
<p>
{actionData.errors.plan}{" "}
<a href="/app/billing">View plans</a>
</p>
</Banner>
)}
<TextField
label="Location name"
name="name"
autoComplete="off"
value={name}
onChange={setName}
error={actionData?.errors?.name}
requiredIndicator
/>
<TextField
label="Address"
name="address"
autoComplete="off"
multiline={2}
value={address}
onChange={setAddress}
/>
<TextField
label="Timezone (IANA, e.g. America/Toronto)"
name="timezone"
autoComplete="off"
value={timezone}
onChange={setTimezone}
error={actionData?.errors?.timezone}
requiredIndicator
helpText="All slot math for this location is computed in this timezone."
/>
<BlockStack>
<Button submit variant="primary" loading={navigation.state === "submitting"}>
Create location
</Button>
</BlockStack>
</FormLayout>
</Form>
</Card>
</Page>
);
}