@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.
Files changed (68) hide show
  1. package/dist/chunk-3QU6BJVF.js +540 -0
  2. package/dist/chunk-4WVAT5M2.js +213 -0
  3. package/dist/chunk-6SBK3D3B.js +347 -0
  4. package/dist/chunk-CHF3YPDV.js +314 -0
  5. package/dist/chunk-FHRO2Z3F.js +174 -0
  6. package/dist/{chunk-DM4KQNJH.js → chunk-FWEYKWKP.js} +1 -1
  7. package/dist/{chunk-FMCHQI7K.js → chunk-KYUVXEJY.js} +34 -12
  8. package/dist/{chunk-F53CPZQO.js → chunk-Q77T2S7B.js} +1 -1
  9. package/dist/chunk-SKG5C3MS.js +379 -0
  10. package/dist/{chunk-GYYUJYI4.js → chunk-X7AU7DV6.js} +2 -3
  11. package/dist/{chunk-XPOZGZTM.js → chunk-XSAVJJVW.js} +11 -4
  12. package/dist/{chunk-6S5FARL3.js → chunk-XWXPERLK.js} +2 -2
  13. package/dist/cookie-consent-banner.cjs +10 -3
  14. package/dist/cookie-consent-banner.d.cts +1 -1
  15. package/dist/cookie-consent-banner.d.ts +1 -1
  16. package/dist/cookie-consent-banner.js +2 -2
  17. package/dist/cookie-settings-view.cjs +2 -3
  18. package/dist/cookie-settings-view.js +2 -2
  19. package/dist/index.cjs +60 -18
  20. package/dist/index.d.cts +8 -7
  21. package/dist/index.d.ts +8 -7
  22. package/dist/index.js +10 -8
  23. package/dist/meet-view.d.cts +1 -1
  24. package/dist/meet-view.d.ts +1 -1
  25. package/dist/network-bar.js +2 -2
  26. package/dist/network-copyright.js +2 -2
  27. package/dist/network.js +1 -1
  28. package/dist/pets-plugin.js +4 -347
  29. package/dist/{snapshot-DmRw8G_B.d.ts → snapshot-CjP7bSXJ.d.ts} +1 -1
  30. package/dist/snapshot-file.d.cts +2 -2
  31. package/dist/snapshot-file.d.ts +2 -2
  32. package/dist/{snapshot-Dx4Lv-lB.d.cts → snapshot-qj4BeNzB.d.cts} +1 -1
  33. package/dist/toys-forms.cjs +609 -0
  34. package/dist/toys-forms.d.cts +35 -0
  35. package/dist/toys-forms.d.ts +35 -0
  36. package/dist/toys-forms.js +4 -0
  37. package/dist/toys-interactions.cjs +594 -0
  38. package/dist/toys-interactions.d.cts +54 -0
  39. package/dist/toys-interactions.d.ts +54 -0
  40. package/dist/toys-interactions.js +520 -0
  41. package/dist/toys-plugin.cjs +713 -0
  42. package/dist/toys-plugin.d.cts +88 -0
  43. package/dist/toys-plugin.d.ts +88 -0
  44. package/dist/toys-plugin.js +10 -0
  45. package/dist/toys-publication.cjs +437 -0
  46. package/dist/toys-publication.d.cts +26 -0
  47. package/dist/toys-publication.d.ts +26 -0
  48. package/dist/toys-publication.js +72 -0
  49. package/dist/toys-server.cjs +275 -0
  50. package/dist/toys-server.d.cts +28 -0
  51. package/dist/toys-server.d.ts +28 -0
  52. package/dist/toys-server.js +4 -0
  53. package/dist/toys-view.cjs +420 -0
  54. package/dist/toys-view.d.cts +55 -0
  55. package/dist/toys-view.d.ts +55 -0
  56. package/dist/toys-view.js +2 -0
  57. package/dist/toys.cjs +196 -0
  58. package/dist/toys.d.cts +278 -0
  59. package/dist/toys.d.ts +278 -0
  60. package/dist/toys.js +1 -0
  61. package/dist/{types-BJFCWWi0.d.cts → types-DBi49N8m.d.cts} +12 -3
  62. package/dist/{types-BJFCWWi0.d.ts → types-DBi49N8m.d.ts} +12 -3
  63. package/dist/verified-card.cjs +34 -12
  64. package/dist/verified-card.d.cts +1 -1
  65. package/dist/verified-card.d.ts +1 -1
  66. package/dist/verified-card.js +1 -1
  67. package/package.json +41 -4
  68. package/toys.css +823 -0
@@ -0,0 +1,594 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/toys.ts
8
+ var TOY_REPORT_REASONS = [
9
+ { key: "recalled_or_unsafe", label: "Recalled or unsafe item" },
10
+ { key: "child_safety", label: "Child safety concern" },
11
+ { key: "harassment", label: "Harassment or threats" },
12
+ { key: "scam_or_payment", label: "Asked for money, deposits or codes" },
13
+ { key: "prohibited_item", label: "Prohibited item" },
14
+ { key: "inappropriate_photo", label: "Inappropriate photo" },
15
+ { key: "no_show", label: "No-show" },
16
+ { key: "misleading_listing", label: "Misleading listing" },
17
+ { key: "spam", label: "Spam" },
18
+ { key: "other", label: "Something else" }
19
+ ];
20
+ var TOY_REQUEST_STATUS_LABELS = {
21
+ pending: "Waiting for the owner",
22
+ accepted: "Accepted \u2014 arrange a meeting",
23
+ declined: "Declined",
24
+ cancelled: "Cancelled",
25
+ completed: "Completed",
26
+ expired: "Expired"
27
+ };
28
+ var TOY_SAFETY = {
29
+ meeting: "Meet only at a listed public place during the day, keep arrangements in this thread, and bring someone along if you like. Never share your home address.",
30
+ money: "ToyRound never involves payments, deposits, shipping fees or verification codes. Anyone asking for them is breaking the rules \u2014 report them."};
31
+ function formatLocalStart(localStart, timezone) {
32
+ const [date, time] = localStart.split("T");
33
+ if (!date || !time) return localStart;
34
+ try {
35
+ const day = (/* @__PURE__ */ new Date(date + "T12:00:00Z")).toLocaleDateString("en-US", {
36
+ weekday: "short",
37
+ month: "short",
38
+ day: "numeric",
39
+ timeZone: "UTC"
40
+ });
41
+ const [h, m] = time.split(":").map(Number);
42
+ const hour12 = (h + 11) % 12 + 1;
43
+ const zone = timezone.split("/").pop()?.replaceAll("_", " ") || timezone;
44
+ return `${day}, ${hour12}:${String(m).padStart(2, "0")} ${h >= 12 ? "PM" : "AM"} (${zone} time)`;
45
+ } catch {
46
+ return localStart;
47
+ }
48
+ }
49
+ var TOY_DAY_LABELS = {
50
+ mon: "Mon",
51
+ tue: "Tue",
52
+ wed: "Wed",
53
+ thu: "Thu",
54
+ fri: "Fri",
55
+ sat: "Sat",
56
+ sun: "Sun"
57
+ };
58
+ function hoursSummary(hours) {
59
+ return Object.entries(hours).map(([day, ranges]) => `${TOY_DAY_LABELS[day] || day} ${ranges.map(([o, c]) => `${o}\u2013${c}`).join(", ")}`).join(" \xB7 ");
60
+ }
61
+ async function toyCall(url, method = "GET", data) {
62
+ const response = await fetch(url, {
63
+ method,
64
+ credentials: "same-origin",
65
+ headers: data instanceof FormData ? void 0 : { "content-type": "application/json" },
66
+ body: data === void 0 ? void 0 : data instanceof FormData ? data : JSON.stringify(data)
67
+ });
68
+ const body = await response.json().catch(() => ({}));
69
+ if (!response.ok) {
70
+ const error = new Error(typeof body.error === "string" ? body.error : body.error?.message || "Please try again.");
71
+ error.code = typeof body.code === "string" ? body.code : void 0;
72
+ throw error;
73
+ }
74
+ return body.data ?? body;
75
+ }
76
+ function ToyCommunityMemory({ selected, communityPath = "/api/toy-community" }) {
77
+ const [error, setError] = react.useState("");
78
+ react.useEffect(() => {
79
+ if (selected === void 0) return;
80
+ void toyCall(communityPath, "POST", { area_id: selected }).catch(() => setError("Your community could not be remembered on this device."));
81
+ }, [selected, communityPath]);
82
+ return error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", role: "status", children: error }) : null;
83
+ }
84
+ function ToySignOut({ returnTo = "/", authPath = "/api/paw-auth" }) {
85
+ const [error, setError] = react.useState("");
86
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
87
+ /* @__PURE__ */ jsxRuntime.jsx(
88
+ "button",
89
+ {
90
+ className: "toy-link-button",
91
+ onClick: async () => {
92
+ try {
93
+ await toyCall(authPath + "/logout", "POST");
94
+ window.location.assign(returnTo);
95
+ } catch {
96
+ setError("Sign-out could not be completed.");
97
+ }
98
+ },
99
+ children: "Sign out"
100
+ }
101
+ ),
102
+ error && /* @__PURE__ */ jsxRuntime.jsxs("span", { role: "alert", children: [
103
+ " ",
104
+ error
105
+ ] })
106
+ ] });
107
+ }
108
+ function ReportForm({ target, apiPath, onDone, reasons = TOY_REPORT_REASONS }) {
109
+ const key = react.useRef("");
110
+ const [busy, setBusy] = react.useState(false);
111
+ return /* @__PURE__ */ jsxRuntime.jsxs(
112
+ "form",
113
+ {
114
+ className: "toy-report",
115
+ onSubmit: async (event) => {
116
+ event.preventDefault();
117
+ const data = new FormData(event.currentTarget);
118
+ setBusy(true);
119
+ try {
120
+ if (!key.current) key.current = crypto.randomUUID();
121
+ await toyCall(`${apiPath}/${target}/reports`, "POST", { reason: data.get("reason"), details: data.get("details"), idempotency_key: key.current });
122
+ onDone("Your private report went to the editors.");
123
+ } catch (e) {
124
+ onDone(e instanceof Error ? e.message : "Please try again.");
125
+ } finally {
126
+ setBusy(false);
127
+ }
128
+ },
129
+ children: [
130
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
131
+ "Reason",
132
+ /* @__PURE__ */ jsxRuntime.jsx("select", { name: "reason", children: reasons.map((r) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: r.key, children: r.label }, r.key)) })
133
+ ] }),
134
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
135
+ "Tell the editors",
136
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { name: "details", required: true, maxLength: 2e3 })
137
+ ] }),
138
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Only editors read reports. Recall and child-safety reports hide a listing immediately pending review." }),
139
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button toy-button-secondary", disabled: busy, children: "Send private report" })
140
+ ]
141
+ }
142
+ );
143
+ }
144
+ function ToyListingActions({ listing, signedIn, apiPath = "/api/toys", accountUrl = "/account", listingPath }) {
145
+ const [reporting, setReporting] = react.useState(false), [blocked, setBlocked] = react.useState(null), [message, setMessage] = react.useState("");
146
+ react.useEffect(() => {
147
+ if (signedIn && listing.owner)
148
+ void toyCall(`${apiPath}/profiles/${listing.owner.id}/block`).then((v) => setBlocked(v.active)).catch(() => setBlocked(null));
149
+ }, [signedIn, listing.owner, apiPath]);
150
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "toy-listing-actions", "aria-label": "Share, report or block", children: [
151
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
152
+ /* @__PURE__ */ jsxRuntime.jsx(
153
+ "button",
154
+ {
155
+ className: "toy-link-button",
156
+ onClick: async () => {
157
+ try {
158
+ if (navigator.share) await navigator.share({ title: listing.content.title, url: window.location.href });
159
+ else {
160
+ await navigator.clipboard.writeText(window.location.href);
161
+ setMessage("Link copied.");
162
+ }
163
+ } catch (error) {
164
+ if (!(error instanceof DOMException && error.name === "AbortError")) setMessage("You can copy the page address to share this listing.");
165
+ }
166
+ },
167
+ children: "Share"
168
+ }
169
+ ),
170
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", "aria-expanded": reporting, onClick: () => setReporting(!reporting), children: "Report this listing" }),
171
+ signedIn && listing.owner && blocked !== null && /* @__PURE__ */ jsxRuntime.jsx(
172
+ "button",
173
+ {
174
+ className: "toy-link-button",
175
+ onClick: async () => {
176
+ try {
177
+ const result = await toyCall(`${apiPath}/profiles/${listing.owner.id}/block`, "PUT", { active: !blocked });
178
+ setBlocked(result.active);
179
+ setMessage(result.active ? "This member is blocked. They can't request your toys or message you." : "Block removed.");
180
+ } catch (e) {
181
+ setMessage(e instanceof Error ? e.message : "Please try again.");
182
+ }
183
+ },
184
+ children: blocked ? "Unblock this member" : "Block this member"
185
+ }
186
+ )
187
+ ] }),
188
+ reporting && (signedIn ? /* @__PURE__ */ jsxRuntime.jsx(
189
+ ReportForm,
190
+ {
191
+ target: listing.id,
192
+ apiPath,
193
+ onDone: (m) => {
194
+ setReporting(false);
195
+ setMessage(m);
196
+ }
197
+ }
198
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: accountUrl + "?next=" + encodeURIComponent(listingPath), children: "Sign in to send a private report." }) })),
199
+ message && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: message })
200
+ ] });
201
+ }
202
+ function ToyRequestPanel({
203
+ listing,
204
+ signedIn,
205
+ ready,
206
+ ownListings = [],
207
+ apiPath = "/api/toys",
208
+ accountUrl = "/account",
209
+ exchangeBasePath = "/exchanges",
210
+ listingPath,
211
+ locationsAvailable
212
+ }) {
213
+ const [open, setOpen] = react.useState(false), [offered, setOffered] = react.useState(""), [note, setNote] = react.useState(""), [busy, setBusy] = react.useState(false), [error, setError] = react.useState(""), [done, setDone] = react.useState();
214
+ const key = react.useRef("");
215
+ const swap = listing.exchange_type === "swap";
216
+ const available = ownListings.filter((l) => l.status === "available");
217
+ if (listing.status !== "available")
218
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", role: "status", children: "This toy is currently reserved for another neighbor. If that exchange doesn't happen, it becomes available again." });
219
+ if (done)
220
+ return /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "status", className: "toy-success", children: [
221
+ "Sent. The owner decides next. ",
222
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `${exchangeBasePath}/${done}`, children: "Open the conversation" }),
223
+ "."
224
+ ] });
225
+ if (!signedIn)
226
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-request-panel", children: [
227
+ /* @__PURE__ */ jsxRuntime.jsx("a", { className: "toy-button", href: accountUrl + "?next=" + encodeURIComponent(listingPath), children: swap ? "Sign in to offer a swap" : "Sign in to request this toy" }),
228
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Requesting needs an adult account with verified email and phone. Browsing never does." })
229
+ ] });
230
+ if (!ready)
231
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-request-panel", children: [
232
+ /* @__PURE__ */ jsxRuntime.jsxs("a", { className: "toy-button", href: accountUrl + "?next=" + encodeURIComponent(listingPath), children: [
233
+ "Finish account setup to ",
234
+ swap ? "offer a swap" : "request this toy"
235
+ ] }),
236
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Confirm you're 18 or older and verify your phone first. It takes a minute." })
237
+ ] });
238
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-request-panel", children: [
239
+ !locationsAvailable && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "No checked meeting places are listed for this community yet. You can send a request, but an exchange can't be scheduled until a public meeting place is added." }),
240
+ !open ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button", onClick: () => setOpen(true), children: swap ? "Offer a swap" : "Request this toy" }) : /* @__PURE__ */ jsxRuntime.jsxs(
241
+ "form",
242
+ {
243
+ className: "toy-form",
244
+ onSubmit: async (event) => {
245
+ event.preventDefault();
246
+ setBusy(true);
247
+ setError("");
248
+ try {
249
+ if (!key.current) key.current = crypto.randomUUID();
250
+ const result = await toyCall(`${apiPath}/${listing.id}/requests`, "POST", {
251
+ kind: swap ? "swap_offer" : "request",
252
+ offered_listing_id: swap ? offered || null : null,
253
+ message: note,
254
+ idempotency_key: key.current
255
+ });
256
+ setDone(result.id);
257
+ } catch (e) {
258
+ setError(e instanceof Error ? e.message : "Please try again.");
259
+ } finally {
260
+ setBusy(false);
261
+ }
262
+ },
263
+ children: [
264
+ swap && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
265
+ "Which of your available toys do you offer?",
266
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { required: true, value: offered, onChange: (e) => setOffered(e.target.value), children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose one of your listed toys" }),
268
+ available.map((l) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: l.id, children: l.content.title }, l.id))
269
+ ] }),
270
+ !available.length && /* @__PURE__ */ jsxRuntime.jsx("small", { children: "You need an approved, available listing of your own to propose a swap." })
271
+ ] }),
272
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
273
+ "A short note (optional)",
274
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { value: note, maxLength: 1e3, onChange: (e) => setNote(e.target.value), placeholder: "When are you usually free to meet?" })
275
+ ] }),
276
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: TOY_SAFETY.money }),
277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
278
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button toy-button-secondary", type: "button", disabled: busy, onClick: () => setOpen(false), children: "Cancel" }),
279
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button", disabled: busy || swap && !offered, children: busy ? "Sending\u2026" : swap ? "Send swap offer" : "Send request" })
280
+ ] }),
281
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "toy-error", children: error })
282
+ ]
283
+ }
284
+ )
285
+ ] });
286
+ }
287
+ function LocationOptions({ locations }) {
288
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: locations.map((l) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: l.id, children: [
289
+ l.name,
290
+ " \u2014 ",
291
+ hoursSummary(l.hours)
292
+ ] }, l.id)) });
293
+ }
294
+ function ToyExchangeThread({ thread, apiPath = "/api/toys", listingBasePath = "/toys", locationsUrl }) {
295
+ const [t, setT] = react.useState(thread), [body, setBody] = react.useState(""), [busy, setBusy] = react.useState(false), [message, setMessage] = react.useState(""), [reporting, setReporting] = react.useState(false), [location, setLocation] = react.useState(thread.locations[0]?.id || ""), [when, setWhen] = react.useState("");
296
+ const mine = t.role;
297
+ const other = t.role === "owner" ? "requester" : "owner";
298
+ const active = ["pending", "accepted"].includes(t.status);
299
+ const refresh = async () => setT(await toyCall(`${apiPath}/requests/${t.id}`));
300
+ const act = async (fn, ok) => {
301
+ setBusy(true);
302
+ setMessage("");
303
+ try {
304
+ await fn();
305
+ await refresh();
306
+ if (ok) setMessage(ok);
307
+ } catch (e) {
308
+ setMessage(e instanceof Error ? e.message : "Please try again.");
309
+ try {
310
+ await refresh();
311
+ } catch {
312
+ }
313
+ } finally {
314
+ setBusy(false);
315
+ }
316
+ };
317
+ const appointment = t.appointment;
318
+ const waitingOnMe = appointment?.status === "proposed" && appointment.proposed_by !== mine;
319
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "toy-thread", "aria-label": "Exchange conversation", children: [
320
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "toy-thread-header", children: [
321
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "toy-context", children: [
322
+ t.kind === "swap_offer" ? "Swap offer" : "Request",
323
+ " \xB7 ",
324
+ TOY_REQUEST_STATUS_LABELS[t.status] || t.status
325
+ ] }),
326
+ /* @__PURE__ */ jsxRuntime.jsxs("h1", { children: [
327
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `${listingBasePath}/${t.listing.id}`, children: t.listing.title }),
328
+ t.offered_listing && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
329
+ " ",
330
+ "\u21C4 ",
331
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `${listingBasePath}/${t.offered_listing.id}`, children: t.offered_listing.title })
332
+ ] })
333
+ ] }),
334
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
335
+ "With ",
336
+ t.counterpart_deleted ? "a neighbor whose account was closed" : t.counterpart?.display_name || "a neighbor",
337
+ t.counterpart && !t.counterpart_deleted && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
338
+ " \xB7 ",
339
+ t.counterpart.completed_exchanges,
340
+ " completed exchange",
341
+ t.counterpart.completed_exchanges === 1 ? "" : "s",
342
+ " (participant-confirmed)"
343
+ ] })
344
+ ] })
345
+ ] }),
346
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-safety", children: t.safety }),
347
+ t.status === "pending" && mine === "owner" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/decision`, "POST", { expected_revision: t.revision, decision: "accept" }), "Accepted. The toy is reserved for this neighbor."), children: "Accept" }),
349
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button toy-button-secondary", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/decision`, "POST", { expected_revision: t.revision, decision: "decline" })), children: "Decline" })
350
+ ] }),
351
+ t.status === "pending" && mine === "requester" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Waiting for the owner to accept or decline." }),
352
+ t.status === "accepted" && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "toy-appointment", "aria-labelledby": "toy-appointment-title", children: [
353
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "toy-appointment-title", children: "Meeting" }),
354
+ appointment ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `toy-appointment-card toy-appointment-${appointment.status}`, children: [
355
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
356
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: appointment.location.name }),
357
+ /* @__PURE__ */ jsxRuntime.jsx("br", {}),
358
+ appointment.location.address,
359
+ " ",
360
+ appointment.location.map_url && /* @__PURE__ */ jsxRuntime.jsx("a", { href: appointment.location.map_url, rel: "noopener noreferrer", children: "map" })
361
+ ] }),
362
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: appointment.starts_at, children: formatLocalStart(appointment.local_start, appointment.timezone) }) }),
363
+ appointment.location.restrictions && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: appointment.location.restrictions }),
364
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: appointment.status === "confirmed" ? "Confirmed by both of you." : waitingOnMe ? "Proposed by the other neighbor \u2014 confirm or propose another time." : "Waiting for the other neighbor to confirm." }),
365
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
366
+ waitingOnMe && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/appointment/confirm`, "POST", { expected_revision: t.revision, appointment_id: appointment.id }), "Confirmed."), children: "Confirm this time" }),
367
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/appointment/cancel`, "POST", { expected_revision: t.revision, appointment_id: appointment.id })), children: "Cancel this appointment" })
368
+ ] })
369
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "No meeting proposed yet." }),
370
+ t.locations.length ? /* @__PURE__ */ jsxRuntime.jsxs(
371
+ "form",
372
+ {
373
+ className: "toy-form toy-propose",
374
+ onSubmit: (event) => {
375
+ event.preventDefault();
376
+ void act(() => toyCall(`${apiPath}/requests/${t.id}/appointment`, "POST", { expected_revision: t.revision, location_id: location, local_start: when }), "Proposed. The other neighbor needs to confirm.");
377
+ },
378
+ children: [
379
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: appointment ? "Propose a different time or place" : "Propose a time and place" }),
380
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
381
+ "Public meeting place",
382
+ /* @__PURE__ */ jsxRuntime.jsx("select", { required: true, value: location, onChange: (e) => setLocation(e.target.value), children: /* @__PURE__ */ jsxRuntime.jsx(LocationOptions, { locations: t.locations }) })
383
+ ] }),
384
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
385
+ "Date and time (local to the place, daytime only)",
386
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "datetime-local", required: true, value: when, onChange: (e) => setWhen(e.target.value), step: 900 })
387
+ ] }),
388
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "toy-note", children: [
389
+ TOY_SAFETY.meeting,
390
+ " ",
391
+ locationsUrl && /* @__PURE__ */ jsxRuntime.jsx("a", { href: locationsUrl, children: "See all meeting places." })
392
+ ] }),
393
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button toy-button-secondary", disabled: busy || !location || !when, children: "Propose" })
394
+ ]
395
+ }
396
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "toy-note", children: [
397
+ "No checked public meeting places are listed for this community yet, so an appointment can't be scheduled here. ",
398
+ locationsUrl && /* @__PURE__ */ jsxRuntime.jsx("a", { href: locationsUrl, children: "Suggest one for editorial review." }),
399
+ " Please don't arrange a private address instead."
400
+ ] }),
401
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
402
+ /* @__PURE__ */ jsxRuntime.jsx(
403
+ "button",
404
+ {
405
+ className: "toy-button",
406
+ disabled: busy || (mine === "owner" ? t.owner_completed : t.requester_completed),
407
+ onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/complete`, "POST", { expected_revision: t.revision }), "Marked complete."),
408
+ children: (mine === "owner" ? t.owner_completed : t.requester_completed) ? "You marked this complete" : "Mark exchange complete"
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/cancel`, "POST", { expected_revision: t.revision }), "Exchange cancelled."), children: "Cancel exchange" })
412
+ ] }),
413
+ (t.owner_completed || t.requester_completed) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Closes when both of you confirm completion." })
414
+ ] }),
415
+ t.status === "pending" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "toy-form-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/requests/${t.id}/cancel`, "POST", { expected_revision: t.revision }), "Cancelled."), children: mine === "requester" ? "Withdraw request" : "Cancel" }) }),
416
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "toy-messages", "aria-label": "Messages", children: [
417
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { children: t.messages.map((m) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: `toy-message toy-message-${m.sender === "system" ? "system" : m.mine ? "mine" : "theirs"}`, children: [
418
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: m.body }),
419
+ m.warnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "toy-warning", role: "note", children: [
420
+ m.warnings.includes("payment") && "Heads up: ToyRound never involves payments, deposits or fees. ",
421
+ m.warnings.includes("verification_code") && "Never share verification codes. ",
422
+ m.warnings.includes("external_link") && "Be careful with outside links. ",
423
+ m.warnings.includes("contact_details") && "Keep coordination here rather than sharing phone numbers. ",
424
+ m.warnings.includes("home_pickup") && "Exchanges happen only at listed public places. "
425
+ ] }),
426
+ /* @__PURE__ */ jsxRuntime.jsxs("small", { children: [
427
+ m.sender === "system" ? "ToyRound" : m.mine ? "You" : t.counterpart?.display_name || other,
428
+ " \xB7 ",
429
+ new Date(m.created_at).toLocaleString()
430
+ ] })
431
+ ] }, m.id)) }),
432
+ active && !t.blocked ? /* @__PURE__ */ jsxRuntime.jsxs(
433
+ "form",
434
+ {
435
+ className: "toy-compose",
436
+ onSubmit: (event) => {
437
+ event.preventDefault();
438
+ const text = body.trim();
439
+ if (!text) return;
440
+ void act(async () => {
441
+ await toyCall(`${apiPath}/requests/${t.id}/messages`, "POST", { body: text });
442
+ setBody("");
443
+ });
444
+ },
445
+ children: [
446
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
447
+ "Message",
448
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { value: body, maxLength: 2e3, onChange: (e) => setBody(e.target.value) })
449
+ ] }),
450
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button", disabled: busy || !body.trim(), children: "Send" })
451
+ ]
452
+ }
453
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: t.blocked ? "This conversation is closed." : "This exchange is closed." })
454
+ ] }),
455
+ !active && t.status !== "declined" && !t.my_feedback && /* @__PURE__ */ jsxRuntime.jsxs(
456
+ "form",
457
+ {
458
+ className: "toy-form toy-feedback",
459
+ onSubmit: (event) => {
460
+ event.preventDefault();
461
+ const data = new FormData(event.currentTarget);
462
+ void act(() => toyCall(`${apiPath}/requests/${t.id}/feedback`, "POST", { outcome: data.get("outcome"), note: data.get("note") || "" }), "Thanks. Feedback is private to the editors.");
463
+ },
464
+ children: [
465
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "How did it go?" }),
466
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
467
+ "Outcome",
468
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { name: "outcome", defaultValue: "completed", children: [
469
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "completed", children: "Went fine" }),
470
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "no_show", children: "The other neighbor didn't show" }),
471
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "item_mismatch", children: "The toy didn't match the listing" }),
472
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "harassment", children: "I felt harassed or unsafe" }),
473
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "other", children: "Something else" })
474
+ ] })
475
+ ] }),
476
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
477
+ "Note for the editors (optional)",
478
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { name: "note", maxLength: 2e3 })
479
+ ] }),
480
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button toy-button-secondary", disabled: busy, children: "Send private feedback" })
481
+ ]
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
485
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", "aria-expanded": reporting, onClick: () => setReporting(!reporting), children: "Report this conversation" }),
486
+ t.counterpart && !t.counterpart_deleted && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", disabled: busy, onClick: () => act(() => toyCall(`${apiPath}/profiles/${t.counterpart.id}/block`, "PUT", { active: !t.blocked }), t.blocked ? "Block removed." : "Blocked. Open exchanges with this member are closed."), children: t.blocked ? "Unblock" : "Block this member" })
487
+ ] }),
488
+ reporting && /* @__PURE__ */ jsxRuntime.jsx(
489
+ ReportForm,
490
+ {
491
+ target: `requests/${t.id}`,
492
+ apiPath,
493
+ onDone: (m) => {
494
+ setReporting(false);
495
+ setMessage(m);
496
+ }
497
+ }
498
+ ),
499
+ message && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: message })
500
+ ] });
501
+ }
502
+ function ToyAccountDelete({ revision, apiPath = "/api/toys", returnTo = "/" }) {
503
+ const [confirm, setConfirm] = react.useState(false), [busy, setBusy] = react.useState(false), [error, setError] = react.useState("");
504
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "toy-delete", "aria-labelledby": "toy-delete-title", children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "toy-delete-title", children: "Delete your ToyRound account" }),
506
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Withdraws your listings, cancels open exchanges and anonymizes your profile. Reports and blocks are retained under the moderation retention policy. Your network sign-in is not affected." }),
507
+ confirm ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "toy-form-actions", children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx(
509
+ "button",
510
+ {
511
+ className: "toy-button toy-button-secondary",
512
+ disabled: busy,
513
+ onClick: async () => {
514
+ setBusy(true);
515
+ try {
516
+ await toyCall(`${apiPath}/member`, "DELETE", { expected_revision: revision });
517
+ window.location.assign(returnTo);
518
+ } catch (e) {
519
+ setError(e instanceof Error ? e.message : "Please try again.");
520
+ setBusy(false);
521
+ }
522
+ },
523
+ children: "Yes, delete my ToyRound account"
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", disabled: busy, onClick: () => setConfirm(false), children: "Keep it" })
527
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-link-button", onClick: () => setConfirm(true), children: "Delete account\u2026" }),
528
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error })
529
+ ] });
530
+ }
531
+ function ToyLocationSuggestForm({ communities, apiPath = "/api/toys", defaultCommunity }) {
532
+ const key = react.useRef("");
533
+ const [busy, setBusy] = react.useState(false), [message, setMessage] = react.useState("");
534
+ return /* @__PURE__ */ jsxRuntime.jsxs(
535
+ "form",
536
+ {
537
+ className: "toy-form toy-suggest",
538
+ onSubmit: async (event) => {
539
+ event.preventDefault();
540
+ const data = new FormData(event.currentTarget);
541
+ setBusy(true);
542
+ setMessage("");
543
+ try {
544
+ if (!key.current) key.current = crypto.randomUUID();
545
+ await toyCall(`${apiPath}/locations/suggestions`, "POST", { area_id: data.get("area_id"), name: data.get("name"), address: data.get("address"), map_url: data.get("map_url") || "", note: data.get("note") || "", idempotency_key: key.current });
546
+ setMessage("Thanks. An editor will check the place before it can be used for exchanges.");
547
+ event.target.reset();
548
+ key.current = "";
549
+ } catch (e) {
550
+ setMessage(e instanceof Error ? e.message : "Please try again.");
551
+ } finally {
552
+ setBusy(false);
553
+ }
554
+ },
555
+ children: [
556
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Suggest a public meeting place" }),
557
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "toy-note", children: "Libraries, police-station lobbies, community centers and busy shops with daytime hours work well. Editors verify every place against a public source before it is listed. Private homes are never accepted." }),
558
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
559
+ "Community",
560
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { name: "area_id", required: true, defaultValue: defaultCommunity || "", children: [
561
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a community" }),
562
+ communities.map((c) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: c.id, children: c.name }, c.id))
563
+ ] })
564
+ ] }),
565
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
566
+ "Place name",
567
+ /* @__PURE__ */ jsxRuntime.jsx("input", { name: "name", required: true, maxLength: 191 })
568
+ ] }),
569
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
570
+ "Address",
571
+ /* @__PURE__ */ jsxRuntime.jsx("input", { name: "address", required: true, maxLength: 500 })
572
+ ] }),
573
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
574
+ "Map or website link (optional, https)",
575
+ /* @__PURE__ */ jsxRuntime.jsx("input", { name: "map_url", type: "url", pattern: "https://.*" })
576
+ ] }),
577
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
578
+ "Why it works (optional)",
579
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { name: "note", maxLength: 2e3 })
580
+ ] }),
581
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "toy-button toy-button-secondary", disabled: busy, children: "Send suggestion" }),
582
+ message && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: message })
583
+ ]
584
+ }
585
+ );
586
+ }
587
+
588
+ exports.ToyAccountDelete = ToyAccountDelete;
589
+ exports.ToyCommunityMemory = ToyCommunityMemory;
590
+ exports.ToyExchangeThread = ToyExchangeThread;
591
+ exports.ToyListingActions = ToyListingActions;
592
+ exports.ToyLocationSuggestForm = ToyLocationSuggestForm;
593
+ exports.ToyRequestPanel = ToyRequestPanel;
594
+ exports.ToySignOut = ToySignOut;
@@ -0,0 +1,54 @@
1
+ import React__default from 'react';
2
+ import { ToyThread, ToyListing, OwnToyListing } from './toys.cjs';
3
+
4
+ declare function ToyCommunityMemory({ selected, communityPath }: {
5
+ selected?: string | null;
6
+ communityPath?: string;
7
+ }): React__default.JSX.Element | null;
8
+ declare function ToySignOut({ returnTo, authPath }: {
9
+ returnTo?: string;
10
+ authPath?: string;
11
+ }): React__default.JSX.Element;
12
+ /** Share, report and block on a public listing; request/offer lives in ToyRequestPanel. */
13
+ declare function ToyListingActions({ listing, signedIn, apiPath, accountUrl, listingPath }: {
14
+ listing: ToyListing;
15
+ signedIn: boolean;
16
+ apiPath?: string;
17
+ accountUrl?: string;
18
+ listingPath: string;
19
+ }): React__default.JSX.Element;
20
+ /** "Request this toy" or "Offer a swap". A giveaway never asks for anything in return. */
21
+ declare function ToyRequestPanel({ listing, signedIn, ready, ownListings, apiPath, accountUrl, exchangeBasePath, listingPath, locationsAvailable, }: {
22
+ listing: ToyListing;
23
+ signedIn: boolean;
24
+ ready: boolean;
25
+ ownListings?: OwnToyListing[];
26
+ apiPath?: string;
27
+ accountUrl?: string;
28
+ exchangeBasePath?: string;
29
+ listingPath: string;
30
+ locationsAvailable: boolean;
31
+ }): React__default.JSX.Element;
32
+ /** The private two-party thread: messages, appointment, completion, feedback, report. */
33
+ declare function ToyExchangeThread({ thread, apiPath, listingBasePath, locationsUrl }: {
34
+ thread: ToyThread;
35
+ apiPath?: string;
36
+ listingBasePath?: string;
37
+ locationsUrl?: string;
38
+ }): React__default.JSX.Element;
39
+ declare function ToyAccountDelete({ revision, apiPath, returnTo }: {
40
+ revision: number;
41
+ apiPath?: string;
42
+ returnTo?: string;
43
+ }): React__default.JSX.Element;
44
+ /** "Suggest a meeting place" enters editorial review; it never becomes usable on its own. */
45
+ declare function ToyLocationSuggestForm({ communities, apiPath, defaultCommunity }: {
46
+ communities: {
47
+ id: string;
48
+ name: string;
49
+ }[];
50
+ apiPath?: string;
51
+ defaultCommunity?: string;
52
+ }): React__default.JSX.Element;
53
+
54
+ export { ToyAccountDelete, ToyCommunityMemory, ToyExchangeThread, ToyListingActions, ToyLocationSuggestForm, ToyRequestPanel, ToySignOut };