@scalemule/news-publication 0.0.61 → 0.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-3QU6BJVF.js +540 -0
- package/dist/chunk-4WVAT5M2.js +213 -0
- package/dist/chunk-6SBK3D3B.js +347 -0
- package/dist/chunk-CHF3YPDV.js +314 -0
- package/dist/chunk-FHRO2Z3F.js +174 -0
- package/dist/{chunk-DM4KQNJH.js → chunk-FWEYKWKP.js} +1 -1
- package/dist/{chunk-FMCHQI7K.js → chunk-KYUVXEJY.js} +34 -12
- package/dist/{chunk-F53CPZQO.js → chunk-Q77T2S7B.js} +1 -1
- package/dist/chunk-SKG5C3MS.js +379 -0
- package/dist/{chunk-GYYUJYI4.js → chunk-X7AU7DV6.js} +2 -3
- package/dist/{chunk-XPOZGZTM.js → chunk-XSAVJJVW.js} +11 -4
- package/dist/{chunk-6S5FARL3.js → chunk-XWXPERLK.js} +2 -2
- package/dist/cookie-consent-banner.cjs +10 -3
- package/dist/cookie-consent-banner.d.cts +1 -1
- package/dist/cookie-consent-banner.d.ts +1 -1
- package/dist/cookie-consent-banner.js +2 -2
- package/dist/cookie-settings-view.cjs +2 -3
- package/dist/cookie-settings-view.js +2 -2
- package/dist/index.cjs +60 -18
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +10 -8
- package/dist/meet-view.d.cts +1 -1
- package/dist/meet-view.d.ts +1 -1
- package/dist/network-bar.js +2 -2
- package/dist/network-copyright.js +2 -2
- package/dist/network.js +1 -1
- package/dist/pets-plugin.js +4 -347
- package/dist/{snapshot-DmRw8G_B.d.ts → snapshot-CjP7bSXJ.d.ts} +1 -1
- package/dist/snapshot-file.d.cts +2 -2
- package/dist/snapshot-file.d.ts +2 -2
- package/dist/{snapshot-Dx4Lv-lB.d.cts → snapshot-qj4BeNzB.d.cts} +1 -1
- package/dist/toys-forms.cjs +609 -0
- package/dist/toys-forms.d.cts +35 -0
- package/dist/toys-forms.d.ts +35 -0
- package/dist/toys-forms.js +4 -0
- package/dist/toys-interactions.cjs +594 -0
- package/dist/toys-interactions.d.cts +54 -0
- package/dist/toys-interactions.d.ts +54 -0
- package/dist/toys-interactions.js +520 -0
- package/dist/toys-plugin.cjs +713 -0
- package/dist/toys-plugin.d.cts +88 -0
- package/dist/toys-plugin.d.ts +88 -0
- package/dist/toys-plugin.js +10 -0
- package/dist/toys-publication.cjs +437 -0
- package/dist/toys-publication.d.cts +26 -0
- package/dist/toys-publication.d.ts +26 -0
- package/dist/toys-publication.js +72 -0
- package/dist/toys-server.cjs +275 -0
- package/dist/toys-server.d.cts +28 -0
- package/dist/toys-server.d.ts +28 -0
- package/dist/toys-server.js +4 -0
- package/dist/toys-view.cjs +420 -0
- package/dist/toys-view.d.cts +55 -0
- package/dist/toys-view.d.ts +55 -0
- package/dist/toys-view.js +2 -0
- package/dist/toys.cjs +196 -0
- package/dist/toys.d.cts +278 -0
- package/dist/toys.d.ts +278 -0
- package/dist/toys.js +1 -0
- package/dist/{types-BJFCWWi0.d.cts → types-DBi49N8m.d.cts} +12 -3
- package/dist/{types-BJFCWWi0.d.ts → types-DBi49N8m.d.ts} +12 -3
- package/dist/verified-card.cjs +34 -12
- package/dist/verified-card.d.cts +1 -1
- package/dist/verified-card.d.ts +1 -1
- package/dist/verified-card.js +1 -1
- package/package.json +41 -4
- package/toys.css +823 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { ToyRoundMark } from './chunk-CHF3YPDV.js';
|
|
3
|
+
import { TOY_SAFETY, TOY_CATEGORIES, TOY_AGE_RANGES, TOY_CONDITIONS, TOY_CPSC_RECALLS_URL, TOY_EXCLUSIONS, TOY_STATUS_LABELS } from './chunk-FHRO2Z3F.js';
|
|
4
|
+
import { useState, useRef, useEffect } from 'react';
|
|
5
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
async function toyCall(url, method = "GET", data) {
|
|
8
|
+
const response = await fetch(url, {
|
|
9
|
+
method,
|
|
10
|
+
credentials: "same-origin",
|
|
11
|
+
headers: data instanceof FormData ? void 0 : { "content-type": "application/json" },
|
|
12
|
+
body: data === void 0 ? void 0 : data instanceof FormData ? data : JSON.stringify(data)
|
|
13
|
+
});
|
|
14
|
+
const body = await response.json().catch(() => ({}));
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const error = new Error(typeof body.error === "string" ? body.error : body.error?.message || "Please try again.");
|
|
17
|
+
error.code = typeof body.code === "string" ? body.code : void 0;
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
return body.data ?? body;
|
|
21
|
+
}
|
|
22
|
+
function ToySignIn({ returnTo = "/account", authPath = "/api/paw-auth" }) {
|
|
23
|
+
const [busy, setBusy] = useState(false), [error, setError] = useState("");
|
|
24
|
+
return /* @__PURE__ */ jsxs("div", { className: "toy-signin", children: [
|
|
25
|
+
/* @__PURE__ */ jsx(
|
|
26
|
+
"button",
|
|
27
|
+
{
|
|
28
|
+
className: "toy-button",
|
|
29
|
+
disabled: busy,
|
|
30
|
+
onClick: async () => {
|
|
31
|
+
setBusy(true);
|
|
32
|
+
setError("");
|
|
33
|
+
try {
|
|
34
|
+
const { url } = await toyCall(authPath + "/start?" + new URLSearchParams({ next: returnTo }), "POST");
|
|
35
|
+
window.location.assign(url);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
setError(e instanceof Error ? e.message : "Sign-in is unavailable.");
|
|
38
|
+
setBusy(false);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
children: busy ? "Opening sign-in\u2026" : "Continue with your network sign-in"
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "toy-error", children: error }),
|
|
45
|
+
/* @__PURE__ */ jsx("p", { className: "toy-note", children: "Your existing Bay Area News Network sign-in works here. Browsing never needs an account." })
|
|
46
|
+
] });
|
|
47
|
+
}
|
|
48
|
+
function ToyMemberSetup({ member, apiPath = "/api/toys", compact = false }) {
|
|
49
|
+
const [name, setName] = useState(member.display_name === "Neighbor" ? "" : member.display_name), [home, setHome] = useState(member.community?.id || ""), [attest, setAttest] = useState(member.verification.adult_attested), [revision, setRevision] = useState(member.revision), [saved, setSaved] = useState(member.verification.adult_attested), [message, setMessage] = useState(""), [busy, setBusy] = useState(false);
|
|
50
|
+
const [phone, setPhone] = useState(""), [code, setCode] = useState(""), [sent, setSent] = useState(false), [phoneVerified, setPhoneVerified] = useState(member.verification.phone_verified), [phoneMessage, setPhoneMessage] = useState("");
|
|
51
|
+
const ready = saved && member.verification.email_verified && phoneVerified;
|
|
52
|
+
return /* @__PURE__ */ jsxs("section", { className: `toy-form toy-setup${compact ? " toy-setup-compact" : ""}`, "aria-labelledby": "toy-setup-title", children: [
|
|
53
|
+
/* @__PURE__ */ jsx("h2", { id: "toy-setup-title", children: ready ? "You're ready to give, swap and request." : "Three quick steps before you can list or request." }),
|
|
54
|
+
/* @__PURE__ */ jsxs("ol", { className: "toy-checklist", children: [
|
|
55
|
+
/* @__PURE__ */ jsxs("li", { className: member.verification.email_verified ? "done" : "", children: [
|
|
56
|
+
/* @__PURE__ */ jsx("strong", { children: "Verified email" }),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { children: member.verification.email_verified ? "Confirmed through your network sign-in." : "Confirm your email from your network account, then sign in again." })
|
|
58
|
+
] }),
|
|
59
|
+
/* @__PURE__ */ jsxs("li", { className: phoneVerified ? "done" : "", children: [
|
|
60
|
+
/* @__PURE__ */ jsx("strong", { children: "Verified phone" }),
|
|
61
|
+
phoneVerified ? /* @__PURE__ */ jsxs("span", { children: [
|
|
62
|
+
"A working mobile number is confirmed. ",
|
|
63
|
+
TOY_SAFETY.verification
|
|
64
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
65
|
+
"form",
|
|
66
|
+
{
|
|
67
|
+
onSubmit: async (event) => {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
setBusy(true);
|
|
70
|
+
setPhoneMessage("");
|
|
71
|
+
try {
|
|
72
|
+
if (!sent) {
|
|
73
|
+
await toyCall(apiPath + "/phone", "POST", { action: "send", phone });
|
|
74
|
+
setSent(true);
|
|
75
|
+
setPhoneMessage("We sent a code by text message.");
|
|
76
|
+
} else {
|
|
77
|
+
await toyCall(apiPath + "/phone", "POST", { action: "verify", phone, code });
|
|
78
|
+
setPhoneVerified(true);
|
|
79
|
+
setPhoneMessage("Phone verified.");
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
setPhoneMessage(e instanceof Error ? e.message : "Please try again.");
|
|
83
|
+
} finally {
|
|
84
|
+
setBusy(false);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
89
|
+
"Mobile number",
|
|
90
|
+
/* @__PURE__ */ jsx("input", { type: "tel", value: phone, onChange: (e) => setPhone(e.target.value), placeholder: "+1 925 555 0100", autoComplete: "tel", required: true })
|
|
91
|
+
] }),
|
|
92
|
+
sent && /* @__PURE__ */ jsxs("label", { children: [
|
|
93
|
+
"Code from the text message",
|
|
94
|
+
/* @__PURE__ */ jsx("input", { inputMode: "numeric", value: code, onChange: (e) => setCode(e.target.value), autoComplete: "one-time-code", required: true })
|
|
95
|
+
] }),
|
|
96
|
+
/* @__PURE__ */ jsx("button", { disabled: busy, children: sent ? "Confirm code" : "Send code" }),
|
|
97
|
+
sent && /* @__PURE__ */ jsx("button", { type: "button", className: "toy-link-button", disabled: busy, onClick: () => setSent(false), children: "Use a different number" }),
|
|
98
|
+
phoneMessage && /* @__PURE__ */ jsx("p", { role: "status", children: phoneMessage }),
|
|
99
|
+
/* @__PURE__ */ jsxs("small", { children: [
|
|
100
|
+
"Your number is used for verification and is never shown to other members. ",
|
|
101
|
+
TOY_SAFETY.verification
|
|
102
|
+
] })
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsxs("li", { className: saved ? "done" : "", children: [
|
|
108
|
+
/* @__PURE__ */ jsx("strong", { children: "Your public name, community and 18+ statement" }),
|
|
109
|
+
/* @__PURE__ */ jsxs(
|
|
110
|
+
"form",
|
|
111
|
+
{
|
|
112
|
+
onSubmit: async (event) => {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
setBusy(true);
|
|
115
|
+
setMessage("");
|
|
116
|
+
try {
|
|
117
|
+
const result = await toyCall(apiPath + "/member", "PUT", {
|
|
118
|
+
expected_revision: revision,
|
|
119
|
+
display_name: name.trim(),
|
|
120
|
+
home_area_id: home || null,
|
|
121
|
+
adult_attestation: { version: member.adult_attestation.version, accepted: attest }
|
|
122
|
+
});
|
|
123
|
+
setRevision(result.revision);
|
|
124
|
+
setSaved(attest);
|
|
125
|
+
setMessage(attest ? "Saved." : "Saved. Confirm the 18+ statement to list or request toys.");
|
|
126
|
+
} catch (e) {
|
|
127
|
+
setMessage(e instanceof Error ? e.message : "Please try again.");
|
|
128
|
+
} finally {
|
|
129
|
+
setBusy(false);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
134
|
+
"First name or display name",
|
|
135
|
+
/* @__PURE__ */ jsx("input", { value: name, onChange: (e) => setName(e.target.value), maxLength: 60, required: true, placeholder: "Shown on your listings" })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
138
|
+
"Home community",
|
|
139
|
+
/* @__PURE__ */ jsxs("select", { value: home, onChange: (e) => setHome(e.target.value), children: [
|
|
140
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Choose a community" }),
|
|
141
|
+
member.communities.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, children: c.name }, c.id))
|
|
142
|
+
] })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
145
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: attest, onChange: (e) => setAttest(e.target.checked) }),
|
|
146
|
+
/* @__PURE__ */ jsx("span", { children: member.adult_attestation.text })
|
|
147
|
+
] }),
|
|
148
|
+
/* @__PURE__ */ jsx("small", { children: "We do not ask for your birth date, identity documents or any child's details." }),
|
|
149
|
+
/* @__PURE__ */ jsx("button", { disabled: busy, children: "Save" }),
|
|
150
|
+
message && /* @__PURE__ */ jsx("p", { role: "status", children: message })
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] })
|
|
155
|
+
] }),
|
|
156
|
+
member.status === "suspended" && /* @__PURE__ */ jsxs("aside", { className: "toy-suspended", role: "status", children: [
|
|
157
|
+
/* @__PURE__ */ jsx("h3", { children: "Your account is suspended from the exchange." }),
|
|
158
|
+
member.moderation_note && /* @__PURE__ */ jsx("p", { children: member.moderation_note }),
|
|
159
|
+
member.suspended_until && /* @__PURE__ */ jsxs("p", { children: [
|
|
160
|
+
"Until ",
|
|
161
|
+
new Date(member.suspended_until).toLocaleDateString(),
|
|
162
|
+
"."
|
|
163
|
+
] }),
|
|
164
|
+
member.appeal_status === "open" ? /* @__PURE__ */ jsx("p", { children: "Your appeal is with the editors." }) : /* @__PURE__ */ jsx(ToyAppealForm, { apiPath, revision, name: name || member.display_name, home, onSaved: setRevision })
|
|
165
|
+
] })
|
|
166
|
+
] });
|
|
167
|
+
}
|
|
168
|
+
function ToyAppealForm({ apiPath, revision, name, home, onSaved }) {
|
|
169
|
+
const [note, setNote] = useState(""), [message, setMessage] = useState(""), [busy, setBusy] = useState(false);
|
|
170
|
+
return /* @__PURE__ */ jsxs(
|
|
171
|
+
"form",
|
|
172
|
+
{
|
|
173
|
+
onSubmit: async (event) => {
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
setBusy(true);
|
|
176
|
+
try {
|
|
177
|
+
const result = await toyCall(apiPath + "/member", "PUT", { expected_revision: revision, display_name: name, home_area_id: home || null, appeal_note: note });
|
|
178
|
+
onSaved(result.revision);
|
|
179
|
+
setMessage("Your appeal was sent to the editors.");
|
|
180
|
+
} catch (e) {
|
|
181
|
+
setMessage(e instanceof Error ? e.message : "Please try again.");
|
|
182
|
+
} finally {
|
|
183
|
+
setBusy(false);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
188
|
+
"Appeal",
|
|
189
|
+
/* @__PURE__ */ jsx("textarea", { value: note, onChange: (e) => setNote(e.target.value), maxLength: 2e3, required: true })
|
|
190
|
+
] }),
|
|
191
|
+
/* @__PURE__ */ jsx("button", { disabled: busy, children: "Send appeal" }),
|
|
192
|
+
message && /* @__PURE__ */ jsx("p", { role: "status", children: message })
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
var emptyContent = {
|
|
198
|
+
title: "",
|
|
199
|
+
description: "",
|
|
200
|
+
brand: "",
|
|
201
|
+
model: "",
|
|
202
|
+
completeness: "complete",
|
|
203
|
+
missing_parts: "",
|
|
204
|
+
age_guidance: "",
|
|
205
|
+
warnings: "",
|
|
206
|
+
swap_wishes: "",
|
|
207
|
+
recall_check: { searched_cpsc: false, searched_at: null, search_terms: "", result: "", notes: "" },
|
|
208
|
+
attestations: { original_photos: false, damage_disclosed: false, not_excluded: false, not_recalled: false }
|
|
209
|
+
};
|
|
210
|
+
function ToyListingForm({
|
|
211
|
+
communities,
|
|
212
|
+
policy,
|
|
213
|
+
initial,
|
|
214
|
+
defaultCommunity,
|
|
215
|
+
apiPath = "/api/toys",
|
|
216
|
+
accountUrl = "/account",
|
|
217
|
+
listingBasePath = "/toys"
|
|
218
|
+
}) {
|
|
219
|
+
const [content, setContent] = useState(initial?.content || emptyContent), [exchangeType, setExchangeType] = useState(initial?.exchange_type || "giveaway"), [category, setCategory] = useState(initial?.category || "building"), [ageRange, setAgeRange] = useState(initial?.age_range || "3-5"), [condition, setCondition] = useState(initial?.condition || "good"), [community, setCommunity] = useState(initial?.area_id || defaultCommunity || ""), [files, setFiles] = useState([]), [step, setStep] = useState(0), [consent, setConsent] = useState(false), [busy, setBusy] = useState(false), [progress, setProgress] = useState(0), [error, setError] = useState(""), [saved, setSaved] = useState();
|
|
220
|
+
const receipt = useRef(initial ? { id: initial.id, revision: initial.revision } : null);
|
|
221
|
+
const key = useRef("");
|
|
222
|
+
const uploads = useRef(/* @__PURE__ */ new Set());
|
|
223
|
+
useEffect(() => () => files.forEach((f) => URL.revokeObjectURL(f.preview)), [files]);
|
|
224
|
+
const change = (field, value) => setContent((c) => ({ ...c, [field]: value }));
|
|
225
|
+
const a = content.attestations;
|
|
226
|
+
async function save(submit) {
|
|
227
|
+
setBusy(true);
|
|
228
|
+
setError("");
|
|
229
|
+
try {
|
|
230
|
+
if (!community) throw new Error("Choose the community where you'll exchange.");
|
|
231
|
+
if (submit && !files.length && !initial?.media.length) throw new Error("Add at least one photo of the actual toy.");
|
|
232
|
+
if (submit && !consent) throw new Error("Read and accept the publication permission.");
|
|
233
|
+
const input = {
|
|
234
|
+
area_id: community,
|
|
235
|
+
exchange_type: exchangeType,
|
|
236
|
+
category,
|
|
237
|
+
age_range: ageRange,
|
|
238
|
+
condition,
|
|
239
|
+
content: { ...content, swap_wishes: exchangeType === "swap" ? content.swap_wishes : "", recall_check: { ...content.recall_check, searched_at: content.recall_check.searched_cpsc ? content.recall_check.searched_at || (/* @__PURE__ */ new Date()).toISOString() : null } },
|
|
240
|
+
consent: consent ? { version: policy.version, accepted: true } : null
|
|
241
|
+
};
|
|
242
|
+
if (!key.current) key.current = crypto.randomUUID();
|
|
243
|
+
if (!receipt.current) {
|
|
244
|
+
const result2 = await toyCall(apiPath, "POST", { ...input, submit: false, idempotency_key: key.current });
|
|
245
|
+
receipt.current = { id: result2.id, revision: result2.revision || 1 };
|
|
246
|
+
} else if (initial && progress === 0 && initial.status !== "draft") {
|
|
247
|
+
}
|
|
248
|
+
setProgress(1);
|
|
249
|
+
for (const [index, photo] of files.entries()) {
|
|
250
|
+
if (!uploads.current.has(photo.key)) {
|
|
251
|
+
const form = new FormData();
|
|
252
|
+
form.set("file", photo.file);
|
|
253
|
+
form.set("caption", content.title);
|
|
254
|
+
form.set("attribution", "");
|
|
255
|
+
form.set("rights_confirmed", "true");
|
|
256
|
+
form.set("consent_version", policy.version);
|
|
257
|
+
form.set("idempotency_key", photo.key);
|
|
258
|
+
await toyCall(`${apiPath}/submissions/${receipt.current.id}/media`, "POST", form);
|
|
259
|
+
uploads.current.add(photo.key);
|
|
260
|
+
}
|
|
261
|
+
setProgress(index + 2);
|
|
262
|
+
}
|
|
263
|
+
const result = await toyCall(`${apiPath}/${receipt.current.id}`, "PUT", { ...input, submit, expected_revision: receipt.current.revision });
|
|
264
|
+
receipt.current = { id: result.id, revision: result.revision };
|
|
265
|
+
setSaved({ id: result.id, status: result.status });
|
|
266
|
+
} catch (e) {
|
|
267
|
+
setError(e instanceof Error ? e.message : "We couldn't save this listing. Please try again.");
|
|
268
|
+
} finally {
|
|
269
|
+
setBusy(false);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (saved)
|
|
273
|
+
return /* @__PURE__ */ jsxs("section", { className: "toy-form", role: "status", children: [
|
|
274
|
+
/* @__PURE__ */ jsx(ToyRoundMark, { size: 56 }),
|
|
275
|
+
/* @__PURE__ */ jsx("h1", { children: saved.status === "draft" ? "Draft saved." : "Thanks \u2014 your listing is in review." }),
|
|
276
|
+
/* @__PURE__ */ jsx("p", { children: saved.status === "draft" ? "Only you can see this draft. Submit it when you're ready." : "A local editor checks the photos, the recall search and the disclosures before it appears. You'll see it under My toys." }),
|
|
277
|
+
/* @__PURE__ */ jsx("a", { className: "toy-button", href: accountUrl, children: "Go to My toys" })
|
|
278
|
+
] });
|
|
279
|
+
return /* @__PURE__ */ jsxs(
|
|
280
|
+
"form",
|
|
281
|
+
{
|
|
282
|
+
className: "toy-form toy-listing-form",
|
|
283
|
+
noValidate: true,
|
|
284
|
+
onSubmit: (event) => {
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
if (!event.currentTarget.reportValidity()) return;
|
|
287
|
+
if (step === 0) {
|
|
288
|
+
setError("");
|
|
289
|
+
setStep(1);
|
|
290
|
+
window.scrollTo({ top: 0 });
|
|
291
|
+
} else void save(true);
|
|
292
|
+
},
|
|
293
|
+
children: [
|
|
294
|
+
/* @__PURE__ */ jsxs("header", { className: "toy-form-intro", children: [
|
|
295
|
+
/* @__PURE__ */ jsxs("ol", { className: "toy-form-steps", "aria-label": "Listing progress", children: [
|
|
296
|
+
/* @__PURE__ */ jsx("li", { "aria-current": step === 0 ? "step" : void 0, children: "1. The toy" }),
|
|
297
|
+
/* @__PURE__ */ jsx("li", { "aria-current": step === 1 ? "step" : void 0, children: "2. Preview & permission" })
|
|
298
|
+
] }),
|
|
299
|
+
/* @__PURE__ */ jsx("h1", { children: step === 0 ? initial ? "Update your listing." : "Give or swap a toy." : "Looks good." }),
|
|
300
|
+
/* @__PURE__ */ jsx("p", { className: "toy-note", children: step === 0 ? "Real photos, honest condition, and a quick recall search. Editors review every listing." : "Check the details. Nothing is published until an editor approves it." })
|
|
301
|
+
] }),
|
|
302
|
+
step === 0 ? /* @__PURE__ */ jsxs("div", { className: "toy-form-body", children: [
|
|
303
|
+
/* @__PURE__ */ jsxs("fieldset", { children: [
|
|
304
|
+
/* @__PURE__ */ jsx("legend", { children: "Giveaway or swap?" }),
|
|
305
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-radio", children: [
|
|
306
|
+
/* @__PURE__ */ jsx("input", { type: "radio", name: "exchange_type", checked: exchangeType === "giveaway", onChange: () => setExchangeType("giveaway") }),
|
|
307
|
+
" Free giveaway \u2014 nothing asked in return"
|
|
308
|
+
] }),
|
|
309
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-radio", children: [
|
|
310
|
+
/* @__PURE__ */ jsx("input", { type: "radio", name: "exchange_type", checked: exchangeType === "swap", onChange: () => setExchangeType("swap") }),
|
|
311
|
+
" Direct swap \u2014 a neighbor offers one of their toys"
|
|
312
|
+
] }),
|
|
313
|
+
exchangeType === "swap" && /* @__PURE__ */ jsxs("label", { children: [
|
|
314
|
+
"What are you hoping to swap for? (optional)",
|
|
315
|
+
/* @__PURE__ */ jsx("input", { value: content.swap_wishes, maxLength: 300, onChange: (e) => change("swap_wishes", e.target.value), placeholder: "A floor puzzle, a toddler bike\u2026" })
|
|
316
|
+
] })
|
|
317
|
+
] }),
|
|
318
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
319
|
+
"Toy name",
|
|
320
|
+
/* @__PURE__ */ jsx("input", { required: true, maxLength: 120, value: content.title, onChange: (e) => change("title", e.target.value), placeholder: "Wooden train set" })
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsxs("div", { className: "toy-field-row", children: [
|
|
323
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
324
|
+
"Category",
|
|
325
|
+
/* @__PURE__ */ jsx("select", { value: category, onChange: (e) => setCategory(e.target.value), children: TOY_CATEGORIES.map((c) => /* @__PURE__ */ jsx("option", { value: c.key, children: c.label }, c.key)) })
|
|
326
|
+
] }),
|
|
327
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
328
|
+
"Manufacturer age guidance",
|
|
329
|
+
/* @__PURE__ */ jsx("select", { value: ageRange, onChange: (e) => setAgeRange(e.target.value), children: TOY_AGE_RANGES.map((c) => /* @__PURE__ */ jsx("option", { value: c.key, children: c.label }, c.key)) })
|
|
330
|
+
] }),
|
|
331
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
332
|
+
"Condition",
|
|
333
|
+
/* @__PURE__ */ jsx("select", { value: condition, onChange: (e) => setCondition(e.target.value), children: TOY_CONDITIONS.map((c) => /* @__PURE__ */ jsx("option", { value: c.key, children: c.label }, c.key)) })
|
|
334
|
+
] })
|
|
335
|
+
] }),
|
|
336
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
337
|
+
"Community for the exchange",
|
|
338
|
+
/* @__PURE__ */ jsxs("select", { required: true, value: community, onChange: (e) => setCommunity(e.target.value), children: [
|
|
339
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Choose a community" }),
|
|
340
|
+
communities.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, children: c.name }, c.id))
|
|
341
|
+
] })
|
|
342
|
+
] }),
|
|
343
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
344
|
+
"Description",
|
|
345
|
+
/* @__PURE__ */ jsx("textarea", { required: true, maxLength: 3e3, value: content.description, onChange: (e) => change("description", e.target.value), placeholder: "What's included, how it's been used, anything a neighbor should know." })
|
|
346
|
+
] }),
|
|
347
|
+
/* @__PURE__ */ jsxs("div", { className: "toy-field-row", children: [
|
|
348
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
349
|
+
"Brand (if known)",
|
|
350
|
+
/* @__PURE__ */ jsx("input", { maxLength: 100, value: content.brand, onChange: (e) => change("brand", e.target.value) })
|
|
351
|
+
] }),
|
|
352
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
353
|
+
"Model or item number (if known)",
|
|
354
|
+
/* @__PURE__ */ jsx("input", { maxLength: 100, value: content.model, onChange: (e) => change("model", e.target.value) })
|
|
355
|
+
] })
|
|
356
|
+
] }),
|
|
357
|
+
/* @__PURE__ */ jsxs("fieldset", { children: [
|
|
358
|
+
/* @__PURE__ */ jsx("legend", { children: "Completeness and damage" }),
|
|
359
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
360
|
+
"Is it complete?",
|
|
361
|
+
/* @__PURE__ */ jsxs("select", { value: content.completeness, onChange: (e) => change("completeness", e.target.value), children: [
|
|
362
|
+
/* @__PURE__ */ jsx("option", { value: "complete", children: "Complete" }),
|
|
363
|
+
/* @__PURE__ */ jsx("option", { value: "missing_parts", children: "Missing parts" }),
|
|
364
|
+
/* @__PURE__ */ jsx("option", { value: "unknown", children: "Not sure" })
|
|
365
|
+
] })
|
|
366
|
+
] }),
|
|
367
|
+
content.completeness === "missing_parts" && /* @__PURE__ */ jsxs("label", { children: [
|
|
368
|
+
"Which parts are missing?",
|
|
369
|
+
/* @__PURE__ */ jsx("input", { required: true, maxLength: 500, value: content.missing_parts, onChange: (e) => change("missing_parts", e.target.value) })
|
|
370
|
+
] }),
|
|
371
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
372
|
+
"Age guidance and warnings printed on the item (if any)",
|
|
373
|
+
/* @__PURE__ */ jsx("input", { maxLength: 200, value: content.age_guidance, onChange: (e) => change("age_guidance", e.target.value), placeholder: "Ages 3+, small parts" })
|
|
374
|
+
] }),
|
|
375
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
376
|
+
"Other manufacturer warnings",
|
|
377
|
+
/* @__PURE__ */ jsx("input", { maxLength: 500, value: content.warnings, onChange: (e) => change("warnings", e.target.value) })
|
|
378
|
+
] })
|
|
379
|
+
] }),
|
|
380
|
+
/* @__PURE__ */ jsxs("fieldset", { className: "toy-recall", children: [
|
|
381
|
+
/* @__PURE__ */ jsx("legend", { children: "Recall check" }),
|
|
382
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
383
|
+
"Search the",
|
|
384
|
+
" ",
|
|
385
|
+
/* @__PURE__ */ jsx("a", { href: TOY_CPSC_RECALLS_URL, target: "_blank", rel: "noopener noreferrer", children: "CPSC recall database" }),
|
|
386
|
+
" ",
|
|
387
|
+
"for the brand and model. Recalled items can't be listed. ",
|
|
388
|
+
TOY_SAFETY.recall
|
|
389
|
+
] }),
|
|
390
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
391
|
+
"What did you search for?",
|
|
392
|
+
/* @__PURE__ */ jsx("input", { maxLength: 200, value: content.recall_check.search_terms, onChange: (e) => change("recall_check", { ...content.recall_check, search_terms: e.target.value }), placeholder: "Brio train 33773" })
|
|
393
|
+
] }),
|
|
394
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
395
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: content.recall_check.searched_cpsc, onChange: (e) => change("recall_check", { ...content.recall_check, searched_cpsc: e.target.checked, searched_at: e.target.checked ? (/* @__PURE__ */ new Date()).toISOString() : null }) }),
|
|
396
|
+
/* @__PURE__ */ jsx("span", { children: "I searched the CPSC recall database for this item." })
|
|
397
|
+
] }),
|
|
398
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
399
|
+
"Result",
|
|
400
|
+
/* @__PURE__ */ jsxs("select", { value: content.recall_check.result, onChange: (e) => change("recall_check", { ...content.recall_check, result: e.target.value }), children: [
|
|
401
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Choose" }),
|
|
402
|
+
/* @__PURE__ */ jsx("option", { value: "no_match_found", children: "I found no recall for this item" }),
|
|
403
|
+
/* @__PURE__ */ jsx("option", { value: "unsure", children: "I'm not sure \u2014 please have an editor check" }),
|
|
404
|
+
/* @__PURE__ */ jsx("option", { value: "possible_match", children: "It may be recalled (cannot be listed)" })
|
|
405
|
+
] })
|
|
406
|
+
] })
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ jsxs("fieldset", { className: "toy-exclusions", children: [
|
|
409
|
+
/* @__PURE__ */ jsx("legend", { children: "Not accepted on ToyRound" }),
|
|
410
|
+
/* @__PURE__ */ jsx("ul", { children: TOY_EXCLUSIONS.map((item) => /* @__PURE__ */ jsx("li", { children: item.label }, item.key)) })
|
|
411
|
+
] }),
|
|
412
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-photo-picker", children: [
|
|
413
|
+
"Photos of the actual toy",
|
|
414
|
+
/* @__PURE__ */ jsx(
|
|
415
|
+
"input",
|
|
416
|
+
{
|
|
417
|
+
type: "file",
|
|
418
|
+
accept: "image/jpeg,image/png,image/webp",
|
|
419
|
+
multiple: true,
|
|
420
|
+
required: !files.length && !initial?.media.length,
|
|
421
|
+
onChange: (e) => {
|
|
422
|
+
const selected = Array.from(e.target.files || []);
|
|
423
|
+
if (selected.length + (initial?.media.length || 0) > 10 || selected.some((f) => f.size > 8 * 1024 * 1024 || !["image/jpeg", "image/png", "image/webp"].includes(f.type))) {
|
|
424
|
+
setError("Choose up to ten JPEG, PNG or WebP photos, each under 8 MB.");
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
setError("");
|
|
428
|
+
setFiles(selected.map((file) => ({ file, key: crypto.randomUUID(), preview: URL.createObjectURL(file) })));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
/* @__PURE__ */ jsx("span", { children: files.length ? `${files.length} photo${files.length === 1 ? "" : "s"} selected` : "Choose photos from your device" })
|
|
433
|
+
] }),
|
|
434
|
+
/* @__PURE__ */ jsx("small", { children: "Location data and other metadata are removed from photos. Please keep children out of the pictures." }),
|
|
435
|
+
files.length > 0 && /* @__PURE__ */ jsx("div", { className: "toy-upload-previews", "aria-label": "Selected photos", children: files.map((file) => /* @__PURE__ */ jsx("img", { src: file.preview, alt: `Selected photo: ${file.file.name}`, width: 120, height: 120 }, file.key)) }),
|
|
436
|
+
initial?.media.map((photo) => /* @__PURE__ */ jsx("img", { className: "toy-existing-photo", src: `${apiPath}/submissions/${initial.id}/media/${photo.id}`, alt: photo.caption || content.title, width: 160, height: 120 }, photo.id)),
|
|
437
|
+
/* @__PURE__ */ jsxs("fieldset", { children: [
|
|
438
|
+
/* @__PURE__ */ jsx("legend", { children: "Your statements" }),
|
|
439
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
440
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", required: true, checked: a.original_photos, onChange: (e) => change("attestations", { ...a, original_photos: e.target.checked }) }),
|
|
441
|
+
/* @__PURE__ */ jsx("span", { children: "These are my own photos of this exact item." })
|
|
442
|
+
] }),
|
|
443
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
444
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", required: true, checked: a.damage_disclosed, onChange: (e) => change("attestations", { ...a, damage_disclosed: e.target.checked }) }),
|
|
445
|
+
/* @__PURE__ */ jsx("span", { children: "I have disclosed all damage, wear and missing pieces." })
|
|
446
|
+
] }),
|
|
447
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
448
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", required: true, checked: a.not_excluded, onChange: (e) => change("attestations", { ...a, not_excluded: e.target.checked }) }),
|
|
449
|
+
/* @__PURE__ */ jsx("span", { children: "This item is not in a category listed above as not accepted." })
|
|
450
|
+
] }),
|
|
451
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
452
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", required: true, checked: a.not_recalled, onChange: (e) => change("attestations", { ...a, not_recalled: e.target.checked }) }),
|
|
453
|
+
/* @__PURE__ */ jsx("span", { children: "To the best of my knowledge this item has not been recalled and is not visibly unsafe." })
|
|
454
|
+
] })
|
|
455
|
+
] })
|
|
456
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "toy-form-body", children: [
|
|
457
|
+
/* @__PURE__ */ jsxs("article", { className: "toy-preview", children: [
|
|
458
|
+
files[0] && /* @__PURE__ */ jsx("img", { src: files[0].preview, alt: `Preview of ${content.title}`, width: 800, height: 600 }),
|
|
459
|
+
/* @__PURE__ */ jsxs("p", { className: "toy-context", children: [
|
|
460
|
+
exchangeType === "swap" ? "Swap" : "Free",
|
|
461
|
+
" \xB7 ",
|
|
462
|
+
communities.find((c) => c.id === community)?.name
|
|
463
|
+
] }),
|
|
464
|
+
/* @__PURE__ */ jsx("h2", { children: content.title }),
|
|
465
|
+
/* @__PURE__ */ jsx("p", { style: { whiteSpace: "pre-wrap" }, children: content.description }),
|
|
466
|
+
/* @__PURE__ */ jsxs("p", { className: "toy-note", children: [
|
|
467
|
+
TOY_CATEGORIES.find((c) => c.key === category)?.label,
|
|
468
|
+
" \xB7 ",
|
|
469
|
+
TOY_AGE_RANGES.find((c) => c.key === ageRange)?.label,
|
|
470
|
+
" \xB7 ",
|
|
471
|
+
TOY_CONDITIONS.find((c) => c.key === condition)?.label
|
|
472
|
+
] })
|
|
473
|
+
] }),
|
|
474
|
+
/* @__PURE__ */ jsxs("details", { children: [
|
|
475
|
+
/* @__PURE__ */ jsx("summary", { children: "Publication permission and guidelines" }),
|
|
476
|
+
/* @__PURE__ */ jsx("p", { children: policy.text }),
|
|
477
|
+
/* @__PURE__ */ jsx("p", { children: policy.guidelines })
|
|
478
|
+
] }),
|
|
479
|
+
/* @__PURE__ */ jsxs("label", { className: "toy-consent", children: [
|
|
480
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", required: true, checked: consent, onChange: (e) => setConsent(e.target.checked) }),
|
|
481
|
+
/* @__PURE__ */ jsx("span", { children: "I have read and accept the publication permission." })
|
|
482
|
+
] })
|
|
483
|
+
] }),
|
|
484
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "toy-error", children: error }),
|
|
485
|
+
busy && /* @__PURE__ */ jsxs("label", { children: [
|
|
486
|
+
"Saving",
|
|
487
|
+
/* @__PURE__ */ jsx("progress", { value: progress, max: files.length + 2 })
|
|
488
|
+
] }),
|
|
489
|
+
/* @__PURE__ */ jsxs("div", { className: "toy-form-actions", children: [
|
|
490
|
+
step === 1 && /* @__PURE__ */ jsx("button", { type: "button", className: "toy-button toy-button-secondary", disabled: busy, onClick: () => setStep(0), children: "Keep editing" }),
|
|
491
|
+
step === 0 && !initial && /* @__PURE__ */ jsx("button", { type: "button", className: "toy-button toy-button-secondary", disabled: busy, onClick: () => void save(false), children: "Save draft" }),
|
|
492
|
+
/* @__PURE__ */ jsx("button", { className: "toy-button", disabled: busy, type: "submit", children: busy ? "Saving\u2026" : step === 0 ? "Preview listing" : "Submit for review" })
|
|
493
|
+
] }),
|
|
494
|
+
/* @__PURE__ */ jsx("p", { className: "toy-note", children: step === 0 ? "Next: a private preview. Nothing is published yet." : "Your listing stays private until an editor approves it." }),
|
|
495
|
+
initial && /* @__PURE__ */ jsxs("p", { className: "toy-note", children: [
|
|
496
|
+
"Current status: ",
|
|
497
|
+
TOY_STATUS_LABELS[initial.status] || initial.status,
|
|
498
|
+
". ",
|
|
499
|
+
/* @__PURE__ */ jsx("a", { href: `${listingBasePath}/${initial.id}`, children: "View listing" })
|
|
500
|
+
] })
|
|
501
|
+
]
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
function ToyOwnerActions({ listing, apiPath = "/api/toys", submitUrl = "/submit" }) {
|
|
506
|
+
const [error, setError] = useState(""), [confirm, setConfirm] = useState(false), [busy, setBusy] = useState(false);
|
|
507
|
+
const editable = !["reserved", "completed", "withdrawn"].includes(listing.status);
|
|
508
|
+
return /* @__PURE__ */ jsxs("div", { className: "toy-form-actions toy-owner-actions", children: [
|
|
509
|
+
editable && /* @__PURE__ */ jsx("a", { href: `${submitUrl}?edit=${listing.id}`, children: "Edit and resubmit" }),
|
|
510
|
+
!["completed", "withdrawn"].includes(listing.status) && (confirm ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
511
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
512
|
+
"Withdraw this listing",
|
|
513
|
+
listing.status === "reserved" ? " and cancel the current exchange" : "",
|
|
514
|
+
"?"
|
|
515
|
+
] }),
|
|
516
|
+
/* @__PURE__ */ jsx(
|
|
517
|
+
"button",
|
|
518
|
+
{
|
|
519
|
+
className: "toy-button toy-button-secondary",
|
|
520
|
+
disabled: busy,
|
|
521
|
+
onClick: async () => {
|
|
522
|
+
setBusy(true);
|
|
523
|
+
try {
|
|
524
|
+
await toyCall(`${apiPath}/${listing.id}`, "DELETE", { expected_revision: listing.revision });
|
|
525
|
+
window.location.reload();
|
|
526
|
+
} catch (e) {
|
|
527
|
+
setError(e instanceof Error ? e.message : "Please try again.");
|
|
528
|
+
setBusy(false);
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
children: "Yes, withdraw"
|
|
532
|
+
}
|
|
533
|
+
),
|
|
534
|
+
/* @__PURE__ */ jsx("button", { className: "toy-button toy-button-secondary", disabled: busy, onClick: () => setConfirm(false), children: "Keep it" })
|
|
535
|
+
] }) : /* @__PURE__ */ jsx("button", { className: "toy-link-button", disabled: busy, onClick: () => setConfirm(true), children: "Withdraw" })),
|
|
536
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
|
|
537
|
+
] });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export { ToyListingForm, ToyMemberSetup, ToyOwnerActions, ToySignIn, toyCall };
|