Skip to content

Commit dedf6be

Browse files
committed
copilot
1 parent 3118839 commit dedf6be

8 files changed

Lines changed: 110 additions & 31 deletions

File tree

messages/de.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,27 @@
111111
"answer": "<p>We only send emails that are necessary to keep Peels working. That includes one or two account-related emails, and emails to notify you whenever a fellow Peels member has sent you message.</p><p>Email notifications are required for listing hosts, as it means a prospective donor has enquired about dropping off scraps (or picking something up). Hosts who longer wish to receive emails can either hide their listing from the map (making it impossible for new donors to reach out) or delete their listing entirely (meaning previous donors can no longer message, either).</p><p>People without listings cannot be messaged (and thus emailed) unless they initiate contact with a host first.</p><p>Anyone can report or block individual Peels members via our messaging system. Blocking someone means they can no longer message or email you.</p>"
112112
}
113113
}
114+
},
115+
"Contact": {
116+
"title": "Kontakt",
117+
"subtitle": "So erreichst du das Peels-Team.",
118+
"via": {
119+
"therot": "Hallo, Leser von The Rot. Danke fürs Vorbeischauen. Hier ist ein direkter Draht zu Danny.",
120+
"general": "Hallo. Hier sind einige E-Mail-Adressen, unter denen du uns erreichen kannst."
121+
},
122+
"contactLabel": "Wenn du",
123+
"contactOptions": {
124+
"general": "Eine allgemeine Anfrage stellen möchtest",
125+
"support": "Hilfe bei etwas brauchst",
126+
"dw": "Mit Danny sprechen möchtest",
127+
"newsletter": "Über den Newsletter sprechen möchtest"
128+
},
129+
"emailLabel": "Am besten schreibst du an",
130+
"copyButton": {
131+
"copied": "Kopiert",
132+
"copying": "Wird kopiert...",
133+
"copyFailed": "Kopieren fehlgeschlagen",
134+
"copyAddress": "Adresse kopieren"
135+
}
114136
}
115137
}

messages/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@
129129
"emailLabel": "You’re best off emailing",
130130
"copyButton": {
131131
"copied": "Copied!",
132+
"copying": "Copying...",
133+
"copyFailed": "Copy failed",
132134
"copyAddress": "Copy address"
133135
}
134136
}

messages/es.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,27 @@
111111
"answer": "<p>We only send emails that are necessary to keep Peels working. That includes one or two account-related emails, and emails to notify you whenever a fellow Peels member has sent you message.</p><p>Email notifications are required for listing hosts, as it means a prospective donor has enquired about dropping off scraps (or picking something up). Hosts who longer wish to receive emails can either hide their listing from the map (making it impossible for new donors to reach out) or delete their listing entirely (meaning previous donors can no longer message, either).</p><p>People without listings cannot be messaged (and thus emailed) unless they initiate contact with a host first.</p><p>Anyone can report or block individual Peels members via our messaging system. Blocking someone means they can no longer message or email you.</p>"
112112
}
113113
}
114+
},
115+
"Contact": {
116+
"title": "Contacto",
117+
"subtitle": "Así puedes contactar con el equipo de Peels.",
118+
"via": {
119+
"therot": "Hola, lector de The Rot. Gracias por pasar por aquí. Esta es una línea directa con Danny.",
120+
"general": "Hola. Aquí tienes algunas direcciones de correo para contactarnos."
121+
},
122+
"contactLabel": "Si quieres",
123+
"contactOptions": {
124+
"general": "Hacer una consulta general",
125+
"support": "Obtener ayuda con algo",
126+
"dw": "Hablar con Danny",
127+
"newsletter": "Hablar sobre el boletín"
128+
},
129+
"emailLabel": "Lo mejor es escribir a",
130+
"copyButton": {
131+
"copied": "Copiado",
132+
"copying": "Copiando...",
133+
"copyFailed": "No se pudo copiar",
134+
"copyAddress": "Copiar dirección"
135+
}
114136
}
115137
}

src/components/Button/Button.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ const buttonStyles = ({ theme }: { theme: any }): any => ({
6868
"&[data-focus]": {
6969
outline: `3px solid ${theme.colors.focus.outline}`,
7070
},
71+
'&[aria-disabled="true"]': {
72+
cursor: "default",
73+
background: theme.colors.button.disabled.background,
74+
color: theme.colors.button.disabled.text,
75+
},
7176

7277
variants: [
7378
{
@@ -122,7 +127,7 @@ const buttonStyles = ({ theme }: { theme: any }): any => ({
122127
"&:not([disabled])": {
123128
boxShadow: `0px 0px 0px 2px ${theme.colors.button.primary.background}`, // Match visual height of sibling buttons with box-shadow
124129
},
125-
"&:hover&:not([disabled])": {
130+
'&:hover&:not([disabled]):not([aria-disabled="true"])': {
126131
background: `color-mix(in srgb, ${theme.colors.button.primary.background}, ${theme.colors.button.primary.hover.tint} ${theme.colors.button.primary.hover.mix})`,
127132
boxShadow: `0px 0px 0px 2px color-mix(in srgb, ${theme.colors.button.primary.background}, ${theme.colors.button.primary.hover.tint} ${theme.colors.button.primary.hover.mix})`,
128133
},
@@ -135,7 +140,7 @@ const buttonStyles = ({ theme }: { theme: any }): any => ({
135140
color: theme.colors.button.secondary.text,
136141
// borderColor: theme.colors.border.base,
137142
boxShadow: `0px 0px 0px 2px ${theme.colors.border.base}`,
138-
"&:hover&:not([disabled])": {
143+
'&:hover&:not([disabled]):not([aria-disabled="true"])': {
139144
color: `color-mix(in srgb, ${theme.colors.button.secondary.text}, ${theme.colors.button.secondary.hover.tint} ${theme.colors.button.secondary.hover.mix})`,
140145
},
141146
},
@@ -147,7 +152,7 @@ const buttonStyles = ({ theme }: { theme: any }): any => ({
147152
color: theme.colors.button.danger.text,
148153
// borderColor: theme.colors.border.base,
149154
boxShadow: `0px 0px 0px 2px ${theme.colors.border.base}`,
150-
"&:hover&:not([disabled])": {
155+
'&:hover&:not([disabled]):not([aria-disabled="true"])': {
151156
color: `color-mix(in srgb, ${theme.colors.button.danger.text}, ${theme.colors.button.danger.hover.tint} ${theme.colors.button.danger.hover.mix})`,
152157
},
153158
},
@@ -159,7 +164,7 @@ const buttonStyles = ({ theme }: { theme: any }): any => ({
159164
border: "none",
160165
color: theme.colors.button.send.text,
161166

162-
"&:hover&:not([disabled])": {
167+
'&:hover&:not([disabled]):not([aria-disabled="true"])': {
163168
backgroundColor: `color-mix(in srgb, ${theme.colors.button.send.text}, ${theme.colors.button.send.hover.tint} ${theme.colors.button.send.hover.mix})`,
164169
},
165170
},
@@ -172,7 +177,7 @@ const buttonStyles = ({ theme }: { theme: any }): any => ({
172177
background: theme.colors.button.secondary.background,
173178
color: theme.colors.button.secondary.text,
174179
border: `1px solid ${theme.colors.border.base}`,
175-
"&:hover&:not([disabled])": {
180+
'&:hover&:not([disabled]):not([aria-disabled="true"])': {
176181
backgroundColor: theme.colors.background.sunk,
177182
},
178183
},
@@ -212,6 +217,7 @@ const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
212217
loadingText = "Loading...",
213218
type = "button",
214219
size = "normal",
220+
onClick,
215221
...props
216222
},
217223
ref
@@ -220,6 +226,15 @@ const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
220226
const isLoading = loading && !isLink;
221227
const isDisabled = disabled || isLoading;
222228
const buttonContent = <span>{isLoading ? loadingText : children}</span>;
229+
const handleLinkClick: React.MouseEventHandler<HTMLElement> = (event) => {
230+
if (isDisabled) {
231+
event.preventDefault();
232+
event.stopPropagation();
233+
return;
234+
}
235+
236+
onClick?.(event);
237+
};
223238

224239
if (href) {
225240
return (
@@ -231,7 +246,7 @@ const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
231246
aria-disabled={isDisabled || undefined}
232247
aria-busy={isLoading || undefined}
233248
size={size}
234-
disabled={isDisabled}
249+
onClick={handleLinkClick}
235250
{...props}
236251
>
237252
{buttonContent}
@@ -249,6 +264,7 @@ const Button = forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(
249264
aria-disabled={isDisabled || undefined}
250265
aria-busy={isLoading || undefined}
251266
size={size}
267+
onClick={onClick}
252268
{...props}
253269
>
254270
{buttonContent}

src/components/EmailSelector/EmailSelector.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,12 @@ export default function EmailSelector() {
115115
<Button
116116
onClick={handleCopy}
117117
loading={copyStatus === "copying"}
118-
loadingText="Copying..."
118+
loadingText={t("copyButton.copying")}
119119
>
120120
{copyStatus === "copied"
121121
? t("copyButton.copied")
122122
: copyStatus === "error"
123-
? "Copy failed"
123+
? t("copyButton.copyFailed")
124124
: t("copyButton.copyAddress")}
125125
</Button>
126126
</SubSectionBottom>

src/components/IconButton/IconButton.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@ const IconButton = forwardRef<
114114
: ariaLabel || iconLabels[icon];
115115

116116
const handleClick: React.MouseEventHandler<HTMLElement> = (e) => {
117-
if (loading || disabled) return;
117+
if (loading || disabled) {
118+
e.preventDefault();
119+
e.stopPropagation();
120+
return;
121+
}
118122

119123
if (onClick) {
120124
onClick(e);

src/components/ListingPhotosManager/ListingPhotosManager.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useRef, useState } from "react";
44
import { uploadListingPhoto, deleteListingPhoto } from "@/utils/mediaUtils";
55
import Button from "@/components/Button";
66
import RemoteImage from "@/components/RemoteImage";
@@ -110,8 +110,19 @@ function ListingPhotosManager({
110110
isNewListing = false,
111111
}: ListingPhotosManagerProps) {
112112
const [photos, setPhotos] = useState(initialPhotos);
113+
const photosRef = useRef(initialPhotos);
113114
const [isUploading, setIsUploading] = useState(false);
114115
const [deletingPhoto, setDeletingPhoto] = useState<string | null>(null);
116+
const isMutatingPhotos = isUploading || deletingPhoto !== null;
117+
118+
const updatePhotos = (
119+
getNextPhotos: (currentPhotos: string[]) => string[]
120+
) => {
121+
const nextPhotos = getNextPhotos(photosRef.current);
122+
photosRef.current = nextPhotos;
123+
setPhotos(nextPhotos);
124+
onPhotosChange?.(nextPhotos);
125+
};
115126

116127
const compressFile = (file: File) => {
117128
return new Promise<File>((resolve, reject) => {
@@ -127,15 +138,15 @@ function ListingPhotosManager({
127138
};
128139

129140
const handleDrop = async (acceptedFiles: File[]) => {
130-
if (isUploading) return;
141+
if (isMutatingPhotos) return;
131142

132143
console.log("Dropped files:", acceptedFiles);
133144

134145
// Convert FileList to Array and process as if they came from input
135146
const files = Array.from(acceptedFiles);
136147

137148
// Reuse existing photo handling logic
138-
if (files.length + photos.length > MAX_PHOTOS) {
149+
if (files.length + photosRef.current.length > MAX_PHOTOS) {
139150
alert(`You can only upload up to ${MAX_PHOTOS} photos`);
140151
return;
141152
}
@@ -168,9 +179,7 @@ function ListingPhotosManager({
168179
uploadListingPhotoAction(file, !isNewListing ? listingSlug : undefined)
169180
);
170181
const newFilenames = (await Promise.all(uploadPromises)) as string[];
171-
const newPhotos = [...photos, ...newFilenames];
172-
setPhotos(newPhotos);
173-
onPhotosChange?.(newPhotos);
182+
updatePhotos((currentPhotos) => [...currentPhotos, ...newFilenames]);
174183
} catch (error) {
175184
// console.error("Upload error details:", error);
176185

@@ -197,39 +206,42 @@ function ListingPhotosManager({
197206
};
198207

199208
const handlePhotoDelete = async (photoToDelete: string) => {
200-
if (deletingPhoto) return;
209+
if (isMutatingPhotos) return;
201210

202211
setDeletingPhoto(photoToDelete);
203212
try {
204213
// Immediately remove from react-beautiful-dnd's context
205-
const newPhotos = photos.filter((photo) => photo !== photoToDelete);
206-
setPhotos(newPhotos);
207-
onPhotosChange?.(newPhotos);
214+
updatePhotos((currentPhotos) =>
215+
currentPhotos.filter((photo) => photo !== photoToDelete)
216+
);
208217

209218
// Then attempt the delete operation
210219
await deleteListingPhotoAction(photoToDelete, listingSlug);
211220
} catch (error) {
212221
console.error("Error deleting photo:", error);
213222

214-
// If the delete fails, revert the UI state
215-
setPhotos(photos);
216-
onPhotosChange?.(photos);
223+
// Restore only the failed deletion, preserving other state changes.
224+
updatePhotos((currentPhotos) =>
225+
currentPhotos.includes(photoToDelete)
226+
? currentPhotos
227+
: [...currentPhotos, photoToDelete]
228+
);
217229
alert("Failed to delete photo. Please try again.");
218230
} finally {
219231
setDeletingPhoto(null);
220232
}
221233
};
222234

223235
const handleDragEnd = (result: DropResult) => {
236+
if (isMutatingPhotos) return;
224237
if (!result.destination) return;
225238

226-
const items = Array.from(photos);
239+
const items = Array.from(photosRef.current);
227240
const [reorderedItem] = items.splice(result.source.index, 1);
228241
items.splice(result.destination.index, 0, reorderedItem);
229242

230243
console.log("Photos reordered:", items);
231-
setPhotos(items);
232-
onPhotosChange?.(items);
244+
updatePhotos(() => items);
233245
};
234246

235247
return (
@@ -243,7 +255,7 @@ function ListingPhotosManager({
243255
droppableId="photos"
244256
direction="vertical"
245257
// Things that react-beautiful-dnd gets mad about me NOT defining...
246-
isDropDisabled={false}
258+
isDropDisabled={isMutatingPhotos}
247259
isCombineEnabled={false}
248260
ignoreContainerClipping={false}
249261
>
@@ -257,6 +269,7 @@ function ListingPhotosManager({
257269
key={filename}
258270
draggableId={filename}
259271
index={index}
272+
isDragDisabled={isMutatingPhotos}
260273
>
261274
{(provided, snapshot) => (
262275
<PhotoItem
@@ -280,7 +293,7 @@ function ListingPhotosManager({
280293
size="small"
281294
loading={deletingPhoto === filename}
282295
loadingText="Deleting..."
283-
disabled={deletingPhoto !== null}
296+
disabled={isMutatingPhotos}
284297
onClick={() => handlePhotoDelete(filename)}
285298
>
286299
Delete
@@ -307,7 +320,7 @@ function ListingPhotosManager({
307320
type="file"
308321
accept="image/*"
309322
multiple
310-
disabled={isUploading || photos.length >= MAX_PHOTOS}
323+
disabled={isMutatingPhotos || photos.length >= MAX_PHOTOS}
311324
style={{ display: "none" }}
312325
id="photo-upload"
313326
/>
@@ -320,7 +333,7 @@ function ListingPhotosManager({
320333
size="small"
321334
loading={isUploading}
322335
loadingText="Uploading..."
323-
disabled={isUploading || photos.length >= MAX_PHOTOS}
336+
disabled={isMutatingPhotos || photos.length >= MAX_PHOTOS}
324337
>
325338
{photos.length > 0 ? "Add more photos" : "Add photos"}
326339
</Button>

src/components/ProfileAccountSettings/ProfileAccountSettings.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ function ProfileAccountSettings({
205205
firstName.reset();
206206
};
207207

208-
const handleNewslettePreferenceUpdate = async (formData: FormData) => {
208+
const handleNewsletterPreferenceUpdate = async (formData: FormData) => {
209209
const nextNewsletterPreference =
210210
formData.get("newsletter_preference") === "true";
211211
console.log("Updating newsletter preference to", nextNewsletterPreference);
@@ -347,7 +347,7 @@ function ProfileAccountSettings({
347347

348348
<ListItem editing={newsletterPreference.isEditing}>
349349
{newsletterPreference.isEditing ? (
350-
<Form nested={true} action={handleNewslettePreferenceUpdate}>
350+
<Form nested={true} action={handleNewsletterPreferenceUpdate}>
351351
<Field>
352352
<Label>Newsletter</Label>
353353
<Select

0 commit comments

Comments
 (0)