From 74e54139aaf1ce935a506101a8c3e03182841352 Mon Sep 17 00:00:00 2001 From: katieyungchung Date: Thu, 30 Apr 2026 21:10:43 -0700 Subject: [PATCH 01/33] Add styling for profile page --- app/(main)/hq/components/AddStoreForm.tsx | 132 ++++++++++-------- .../components/DeleteStoreItemButton.tsx | 2 +- .../add/components/StoreItemsDonationForm.tsx | 5 +- app/(main)/profile/components/ProfileForm.tsx | 111 ++++++++------- .../components/AddInStockToCartForm.tsx | 5 +- .../components/AddOutOfStockToCartForm.tsx | 5 +- app/globals.css | 22 ++- 7 files changed, 155 insertions(+), 127 deletions(-) diff --git a/app/(main)/hq/components/AddStoreForm.tsx b/app/(main)/hq/components/AddStoreForm.tsx index 3ac5d9a7..8442457e 100644 --- a/app/(main)/hq/components/AddStoreForm.tsx +++ b/app/(main)/hq/components/AddStoreForm.tsx @@ -111,69 +111,77 @@ export default function AddStoreForm() { return (
-
-
-
- Profile photo - - {/* Only show Remove if there is currently a photo and we aren't already deleting it */} - {displayImage !== defaultStorePhoto.src && ( - - )} - -
- -
- -
- - -
+ +
+
+ Profile photo + + {/* Only show Remove if there is currently a photo and we aren't already deleting it */} + {displayImage !== defaultStorePhoto.src && ( + + )} + +
+ +
+ +
+ + +
+
+ +
+ + +
+ +
+ {bothFilled && ( + + )} + + {eitherFilled && ( + + )} +
+
- -
- - -
- -
- {bothFilled && ( - - )} - - {eitherFilled && ( - - )} -
- -
); } diff --git a/app/(main)/manage/[storeId]/[storeItemId]/components/DeleteStoreItemButton.tsx b/app/(main)/manage/[storeId]/[storeItemId]/components/DeleteStoreItemButton.tsx index d44a0df8..6f87141b 100644 --- a/app/(main)/manage/[storeId]/[storeItemId]/components/DeleteStoreItemButton.tsx +++ b/app/(main)/manage/[storeId]/[storeItemId]/components/DeleteStoreItemButton.tsx @@ -30,7 +30,7 @@ export default function DeleteStoreItemButton({ } return ( - ); diff --git a/app/(main)/manage/[storeId]/add/components/StoreItemsDonationForm.tsx b/app/(main)/manage/[storeId]/add/components/StoreItemsDonationForm.tsx index 21e95011..f04cba69 100644 --- a/app/(main)/manage/[storeId]/add/components/StoreItemsDonationForm.tsx +++ b/app/(main)/manage/[storeId]/add/components/StoreItemsDonationForm.tsx @@ -201,10 +201,7 @@ export default function StoreItemsDonationForm({ // add autofillitems connection pass in prop to storeitemsform )} {itemSettingsSelected?.includes('addInventoryItems') && ( - )} diff --git a/app/(main)/profile/components/ProfileForm.tsx b/app/(main)/profile/components/ProfileForm.tsx index 485b18ec..a3e2c680 100644 --- a/app/(main)/profile/components/ProfileForm.tsx +++ b/app/(main)/profile/components/ProfileForm.tsx @@ -155,57 +155,66 @@ export default function ProfileForm({ user }: { user: User }) { const hasDirtyTextOrImage = isDirty || !!selectedFile || isPendingDelete; return ( -
-
- Profile photo - - {/* Only show Remove if there is currently a photo and we aren't already deleting it */} - {!isPendingDelete && displayImage !== defaultProfilePhoto.src && ( - - )} - -
- +
+
+ +
+ Profile photo + + {/* Only show Remove if there is currently a photo and we aren't already deleting it */} + {!isPendingDelete && displayImage !== defaultProfilePhoto.src && ( + + )} + +
+ +
+ +
+
+ + + {errors.firstName?.type === 'required' && ( +

First name is required.

+ )} +
+ +
+ + + {errors.lastName?.type === 'required' && ( +

Last name is required.

+ )} +
+
+ + + + {errors.email?.type === 'required' && ( +

Email is required.

+ )} + + {hasDirtyTextOrImage && ( + <> + + + + )} +
- - - - {errors.firstName?.type === 'required' && ( -

First name is required.

- )} -
- - - {errors.lastName?.type === 'required' && ( -

Last name is required.

- )} -
- - - {errors.email?.type === 'required' && ( -

Email is required.

- )} -
- - {hasDirtyTextOrImage && ( - <> - - - - )} - +
); } diff --git a/app/(main)/request/[storeId]/[storeItemId]/components/AddInStockToCartForm.tsx b/app/(main)/request/[storeId]/[storeItemId]/components/AddInStockToCartForm.tsx index bfaf171c..9e45d1ab 100644 --- a/app/(main)/request/[storeId]/[storeItemId]/components/AddInStockToCartForm.tsx +++ b/app/(main)/request/[storeId]/[storeItemId]/components/AddInStockToCartForm.tsx @@ -34,10 +34,7 @@ export default function AddInStockToCartForm({ required /> - diff --git a/app/(main)/request/components/AddOutOfStockToCartForm.tsx b/app/(main)/request/components/AddOutOfStockToCartForm.tsx index bede2434..cf176a79 100644 --- a/app/(main)/request/components/AddOutOfStockToCartForm.tsx +++ b/app/(main)/request/components/AddOutOfStockToCartForm.tsx @@ -31,10 +31,7 @@ export default function AddOutOfStockToCartForm({ type="text" placeholder="Description of item..." /> - diff --git a/app/globals.css b/app/globals.css index 5c7fed4c..e55a5c3c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -96,6 +96,26 @@ thead th { opacity: 1; } +.form-card .card-body input { + width: 100%; + padding: 8px 12px; + border: 1px solid #e2e8f0; + border-radius: 5px; + font-family: inherit; + font-size: 1rem; + color: #2d3748; + background: #fff; + outline: none; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; +} + +.form-card .card-body input:focus { + border-color: #2d3748; + box-shadow: 0 0 0 3px rgba(45, 55, 72, 0.1); +} + .field-label { margin-bottom: 5px; } @@ -184,4 +204,4 @@ thead th { .submit-button-row { display: flex; gap: 12px; -} \ No newline at end of file +} From 10b70a4d7c98c39ee9d64b41e8291f95d81af404 Mon Sep 17 00:00:00 2001 From: katieyungchung Date: Mon, 4 May 2026 21:20:24 -0700 Subject: [PATCH 02/33] Add image drag and drop and positioning --- app/(main)/components/PhotoUpload.tsx | 15 ++- app/(main)/components/ProfilePage.module.css | 90 ++++++++++++++++ app/(main)/profile/components/ProfileForm.tsx | 101 ++++++++++++++++-- .../profile/components/UpdatePasswordForm.tsx | 81 +++++++------- app/(main)/profile/page.tsx | 4 +- app/globals.css | 20 ---- public/image-upload.svg | 5 + 7 files changed, 246 insertions(+), 70 deletions(-) create mode 100644 app/(main)/components/ProfilePage.module.css create mode 100644 public/image-upload.svg diff --git a/app/(main)/components/PhotoUpload.tsx b/app/(main)/components/PhotoUpload.tsx index ef9ab79a..6cd8a567 100644 --- a/app/(main)/components/PhotoUpload.tsx +++ b/app/(main)/components/PhotoUpload.tsx @@ -6,9 +6,10 @@ import React from 'react'; type FileUploaderProps = { onFileSelect: (file: File) => void; inputRef: React.RefObject; + id?: string; }; -const FileUploader = ({ onFileSelect, inputRef }: FileUploaderProps) => { +const FileUploader = ({ onFileSelect, inputRef, id }: FileUploaderProps) => { const handleFileChange = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { @@ -19,6 +20,7 @@ const FileUploader = ({ onFileSelect, inputRef }: FileUploaderProps) => { return ( { type PhotoUploadProps = { onFileSelect?: (file: File) => void; + id?: string; }; const PhotoUpload = forwardRef<{ resetFile: () => void }, PhotoUploadProps>( - ({ onFileSelect }, ref) => { + ({ onFileSelect, id }, ref) => { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ @@ -46,7 +49,13 @@ const PhotoUpload = forwardRef<{ resetFile: () => void }, PhotoUploadProps>( onFileSelect?.(file); }; - return ; + return ( + + ); }, ); diff --git a/app/(main)/components/ProfilePage.module.css b/app/(main)/components/ProfilePage.module.css new file mode 100644 index 00000000..93a0a2a4 --- /dev/null +++ b/app/(main)/components/ProfilePage.module.css @@ -0,0 +1,90 @@ +.photoSection { + display: flex; + flex-direction: column; + align-items: center; + margin-top: -150px; + margin-bottom: 24px; +} + +.photoCircle { + width: 200px; + height: 200px; + border-radius: 50%; + overflow: hidden; + border: 1px dashed #cbd5e0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background: #fff; + cursor: pointer; + transition: + border-color 0.2s ease, + background 0.2s ease; + position: relative; +} + +.photoCircleDragging { + border-color: #2d3748; + background: #e2e8f0; + transform: scale(1.03); +} + +.photoCircle:hover { + border-color: #2d3748; + background: #edf2f7; +} + +.photoCircle img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.photoPlaceholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + padding: 12px; + text-align: center; + color: #000000; +} + +.photoPlaceholder svg { + width: 28px; + height: 28px; + stroke: #a0aec0; +} + +.photoPlaceholderText { + font-size: 0.65rem; + line-height: 1.3; +} + +.formBody { + display: flex; + flex-direction: column; + gap: 20px; +} + +.input { + width: 100%; + padding: 8px 12px; + border: 1px solid #e2e8f0; + border-radius: 5px; + font-family: inherit; + font-size: 1rem; + color: #2d3748; + background: #fff; + outline: none; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; +} + +.input:focus { + border-color: #2d3748; + box-shadow: 0 0 0 3px rgba(45, 55, 72, 0.1); +} diff --git a/app/(main)/profile/components/ProfileForm.tsx b/app/(main)/profile/components/ProfileForm.tsx index a3e2c680..f2aa5ec3 100644 --- a/app/(main)/profile/components/ProfileForm.tsx +++ b/app/(main)/profile/components/ProfileForm.tsx @@ -8,6 +8,8 @@ import { useState, useRef } from 'react'; import { createClient } from '@/app/lib/supabase/browser-client'; import PhotoUpload from '@/app/(main)/components/PhotoUpload'; import defaultProfilePhoto from '@/public/default-profile-picture.png'; +import styles from '@/app/(main)/components/ProfilePage.module.css'; +import uploadPhotoIcon from '@/public/image-upload.svg'; type ProfileFormValues = { email: string; @@ -30,6 +32,9 @@ export default function ProfileForm({ user }: { user: User }) { const supabase = createClient(); const photoUploadRef = useRef<{ resetFile: () => void }>(null); + // For drag and drop image file + const [isDragging, setIsDragging] = useState(false); + const { register, handleSubmit, @@ -63,6 +68,24 @@ export default function ProfileForm({ user }: { user: User }) { photoUploadRef.current?.resetFile(); }; + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleDragLeave = () => { + setIsDragging(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragging(false); + const file = e.dataTransfer.files?.[0]; + if (file && file.type.startsWith('image/')) { + handleFileSelect(file); + } + }; + const onCancel = () => { if (previewUrl) URL.revokeObjectURL(previewUrl); setPreviewUrl(null); @@ -155,10 +178,11 @@ export default function ProfileForm({ user }: { user: User }) { const hasDirtyTextOrImage = isDirty || !!selectedFile || isPendingDelete; return ( -
+
-
+

User Information

+ {/*
Profile photo - {/* Only show Remove if there is currently a photo and we aren't already deleting it */} {!isPendingDelete && displayImage !== defaultProfilePhoto.src && (
*/} +
+ {/* Clicking the circle triggers the hidden PhotoUpload input */} + + + {!isPendingDelete && displayImage !== defaultProfilePhoto.src && ( + + )} + + {/* Hidden — triggered by clicking the circle label */} +
- + {errors.firstName?.type === 'required' && (

First name is required.

)} @@ -190,7 +273,10 @@ export default function ProfileForm({ user }: { user: User }) {
- + {errors.lastName?.type === 'required' && (

Last name is required.

)} @@ -198,7 +284,10 @@ export default function ProfileForm({ user }: { user: User }) {
- + {errors.email?.type === 'required' && (

Email is required.

)} diff --git a/app/(main)/profile/components/UpdatePasswordForm.tsx b/app/(main)/profile/components/UpdatePasswordForm.tsx index 3fac01d0..0c5f4d1a 100644 --- a/app/(main)/profile/components/UpdatePasswordForm.tsx +++ b/app/(main)/profile/components/UpdatePasswordForm.tsx @@ -53,44 +53,49 @@ export default function UpdatePasswordForm() { }; return ( - - - - {errors.newPassword && ( -

- Password must be at least 8 characters and include an uppercase - letter, a lowercase letter, a number, and a symbol. -

- )} -
- - +
+
+ +

Privacy

+ + + {errors.newPassword && ( +

+ Password must be at least 8 characters and include an uppercase + letter, a lowercase letter, a number, and a symbol. +

+ )} +
+ + - {newPasswordConfirmation.length > 0 && !passwordsMatch && ( -

Passwords do not match.

- )} -
- {(newPassword.length > 0 || newPasswordConfirmation.length > 0) && ( - - )} - {passwordsMatch && } - + {newPasswordConfirmation.length > 0 && !passwordsMatch && ( +

Passwords do not match.

+ )} +
+ {(newPassword.length > 0 || newPasswordConfirmation.length > 0) && ( + + )} + {passwordsMatch && } + +
+
); } diff --git a/app/(main)/profile/page.tsx b/app/(main)/profile/page.tsx index cd277d75..2ce50f94 100644 --- a/app/(main)/profile/page.tsx +++ b/app/(main)/profile/page.tsx @@ -26,11 +26,9 @@ export default async function PersonalProfilePage() { } return ( -
+

Profile

-

Public Profile

-

Authentication

diff --git a/app/globals.css b/app/globals.css index e55a5c3c..223ba553 100644 --- a/app/globals.css +++ b/app/globals.css @@ -96,26 +96,6 @@ thead th { opacity: 1; } -.form-card .card-body input { - width: 100%; - padding: 8px 12px; - border: 1px solid #e2e8f0; - border-radius: 5px; - font-family: inherit; - font-size: 1rem; - color: #2d3748; - background: #fff; - outline: none; - transition: - border-color 0.2s ease, - box-shadow 0.2s ease; -} - -.form-card .card-body input:focus { - border-color: #2d3748; - box-shadow: 0 0 0 3px rgba(45, 55, 72, 0.1); -} - .field-label { margin-bottom: 5px; } diff --git a/public/image-upload.svg b/public/image-upload.svg new file mode 100644 index 00000000..b5ddd089 --- /dev/null +++ b/public/image-upload.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From d313f3c2caba9a5160c0798b5d34795f7d9b88f0 Mon Sep 17 00:00:00 2001 From: katieyungchung Date: Mon, 4 May 2026 21:31:57 -0700 Subject: [PATCH 03/33] Profile photo styling fixes --- app/(main)/components/PhotoUpload.tsx | 1 + app/(main)/components/ProfilePage.module.css | 6 +++- app/(main)/profile/components/ProfileForm.tsx | 31 ++----------------- app/(main)/profile/page.tsx | 2 +- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/app/(main)/components/PhotoUpload.tsx b/app/(main)/components/PhotoUpload.tsx index 6cd8a567..f6c3724e 100644 --- a/app/(main)/components/PhotoUpload.tsx +++ b/app/(main)/components/PhotoUpload.tsx @@ -24,6 +24,7 @@ const FileUploader = ({ onFileSelect, inputRef, id }: FileUploaderProps) => { type="file" accept="image/*" onChange={handleFileChange} + style={{ display: 'none' }} /> ); }; diff --git a/app/(main)/components/ProfilePage.module.css b/app/(main)/components/ProfilePage.module.css index 93a0a2a4..e7f66067 100644 --- a/app/(main)/components/ProfilePage.module.css +++ b/app/(main)/components/ProfilePage.module.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; align-items: center; - margin-top: -150px; + margin-top: -180px; margin-bottom: 24px; } @@ -24,6 +24,10 @@ position: relative; } +.photoCircleWithImage { + border: none; +} + .photoCircleDragging { border-color: #2d3748; background: #e2e8f0; diff --git a/app/(main)/profile/components/ProfileForm.tsx b/app/(main)/profile/components/ProfileForm.tsx index f2aa5ec3..87b4eaf9 100644 --- a/app/(main)/profile/components/ProfileForm.tsx +++ b/app/(main)/profile/components/ProfileForm.tsx @@ -182,30 +182,11 @@ export default function ProfileForm({ user }: { user: User }) {

User Information

- {/*
- Profile photo - - {!isPendingDelete && displayImage !== defaultProfilePhoto.src && ( - - )} - -
- -
*/}
{/* Clicking the circle triggers the hidden PhotoUpload input */}