photo ui update

This commit is contained in:
2026-04-21 11:22:14 +05:30
parent 8f6c705677
commit 4500fde334
4 changed files with 82 additions and 57 deletions

View File

@@ -1308,13 +1308,20 @@
} }
.settings-profile-name { .settings-profile-name {
margin: 0 0 0.3rem; margin: 0 0 0.15rem;
font-size: 1.0625rem; font-size: 1.0625rem;
font-weight: 700; font-weight: 700;
color: #1a1a1a; color: #1a1a1a;
font-family: "Sniglet", system-ui; font-family: "Sniglet", system-ui;
} }
.settings-profile-email {
margin: 0;
font-size: 0.75rem;
color: var(--color-text-muted);
font-family: "Sniglet", system-ui;
}
.settings-profile-badge { .settings-profile-badge {
display: inline-block; display: inline-block;
padding: 0.15rem 0.5rem; padding: 0.15rem 0.5rem;
@@ -1360,8 +1367,7 @@
position: relative; position: relative;
width: 80px; width: 80px;
height: 80px; height: 80px;
margin: 0 auto 0.5rem; margin: 0 auto 0.75rem;
cursor: pointer;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
} }
@@ -1387,30 +1393,44 @@
font-family: "Sniglet", system-ui; font-family: "Sniglet", system-ui;
} }
.edit-modal-avatar-overlay { .edit-modal-photo-actions {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.35);
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
color: #fff; gap: 0.5rem;
border-radius: 50%; margin-bottom: 0.75rem;
}
.edit-modal-change-photo,
.edit-modal-remove-photo {
flex: 1;
max-width: 130px;
padding: 0.45rem 0.75rem;
border-radius: 8px;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
font-family: "Sniglet", system-ui;
transition: background 0.15s;
}
.edit-modal-change-photo {
background: var(--color-accent-light, #dcfce7);
border: 1.5px solid var(--color-primary, #22c55e);
color: var(--color-primary, #22c55e);
}
.edit-modal-change-photo:hover {
background: #bbf7d0;
} }
.edit-modal-remove-photo { .edit-modal-remove-photo {
display: block; background: #fef2f2;
margin: 0 auto 0.5rem; border: 1.5px solid #fca5a5;
background: none;
border: none;
color: #ef4444; color: #ef4444;
font-size: 0.75rem;
cursor: pointer;
padding: 0.25rem 0.5rem;
} }
.edit-modal-remove-photo:hover { .edit-modal-remove-photo:hover {
text-decoration: underline; background: #fee2e2;
} }
.edit-modal-save { .edit-modal-save {
@@ -1653,7 +1673,8 @@
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
gap: 0.5rem;
padding: 0.875rem 1.125rem; padding: 0.875rem 1.125rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
font-size: 0.9375rem; font-size: 0.9375rem;

View File

@@ -164,7 +164,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// Try to get existing user // Try to get existing user
try { try {
console.log('[Auth] Fetching user by email:', email) // console.log('[Auth] Fetching user by email:', email)
const existingUser = await getUserByEmail(email, token) as MongoUser const existingUser = await getUserByEmail(email, token) as MongoUser
setUserId(existingUser.id) setUserId(existingUser.id)
setMongoUser(existingUser) setMongoUser(existingUser)
@@ -179,7 +179,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, },
token token
) as MongoUser ) as MongoUser
console.log('[Auth] Registered new user:', newUser.id) // console.log('[Auth] Registered new user:', newUser.id)
setUserId(newUser.id) setUserId(newUser.id)
setMongoUser(newUser) setMongoUser(newUser)
syncReminderFromDb(newUser) syncReminderFromDb(newUser)

View File

@@ -35,11 +35,11 @@ async function getFcmToken(): Promise<string | null> {
} }
const swReg = await navigator.serviceWorker.ready const swReg = await navigator.serviceWorker.ready
console.log('[FCM] Service worker ready:', swReg.active?.scriptURL) // console.log('[FCM] Service worker ready:', swReg.active?.scriptURL)
const token = await getToken(messaging, { vapidKey: VAPID_KEY, serviceWorkerRegistration: swReg }) const token = await getToken(messaging, { vapidKey: VAPID_KEY, serviceWorkerRegistration: swReg })
if (token) { if (token) {
console.log('[FCM] Token obtained:', token.slice(0, 20) + '…') // console.log('[FCM] Token obtained:', token.slice(0, 20) + '…')
} else { } else {
console.warn('[FCM] getToken returned empty — VAPID key wrong or SW not registered?') console.warn('[FCM] getToken returned empty — VAPID key wrong or SW not registered?')
} }
@@ -74,13 +74,13 @@ export async function enableReminder(
} }
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
console.log('[FCM] Saving token and reminder settings:', { timeStr, timezone }) // console.log('[FCM] Saving token and reminder settings:', { timeStr, timezone })
await saveFcmToken(userId, fcmToken, authToken) await saveFcmToken(userId, fcmToken, authToken)
console.log('[FCM] Token saved to backend') // console.log('[FCM] Token saved to backend')
await saveReminderSettings(userId, { time: timeStr, enabled: true, timezone }, authToken) await saveReminderSettings(userId, { time: timeStr, enabled: true, timezone }, authToken)
console.log('[FCM] Reminder settings saved to backend') // console.log('[FCM] Reminder settings saved to backend')
localStorage.setItem(REMINDER_TIME_KEY, timeStr) localStorage.setItem(REMINDER_TIME_KEY, timeStr)
localStorage.setItem(REMINDER_ENABLED_KEY, 'true') localStorage.setItem(REMINDER_ENABLED_KEY, 'true')
@@ -113,10 +113,10 @@ export async function listenForegroundMessages(): Promise<() => void> {
const messaging = await messagingPromise const messaging = await messagingPromise
if (!messaging) return () => {} if (!messaging) return () => {}
console.log('[FCM] Foreground message listener registered') // console.log('[FCM] Foreground message listener registered')
const unsubscribe = onMessage(messaging, (payload) => { const unsubscribe = onMessage(messaging, (payload) => {
console.log('[FCM] Foreground message received:', payload) // console.log('[FCM] Foreground message received:', payload)
const title = payload.notification?.title || 'Grateful Journal 🌱' const title = payload.notification?.title || 'Grateful Journal 🌱'
const body = payload.notification?.body || "You haven't written today yet." const body = payload.notification?.body || "You haven't written today yet."
if (Notification.permission !== 'granted') { if (Notification.permission !== 'granted') {

View File

@@ -348,6 +348,7 @@ export default function SettingsPage() {
</div> </div>
<div className="settings-profile-info"> <div className="settings-profile-info">
<h2 className="settings-profile-name">{displayName}</h2> <h2 className="settings-profile-name">{displayName}</h2>
{user?.email && <p className="settings-profile-email">{user.email}</p>}
</div> </div>
<button type="button" className="settings-edit-btn" onClick={openEditModal} title="Edit profile"> <button type="button" className="settings-edit-btn" onClick={openEditModal} title="Edit profile">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -358,11 +359,11 @@ export default function SettingsPage() {
</div> </div>
{/* Privacy & Security */} {/* Privacy & Security */}
<section className="settings-section"> {/* <section className="settings-section"> */}
<h3 className="settings-section-title">PRIVACY & SECURITY</h3> {/* <h3 className="settings-section-title">PRIVACY & SECURITY</h3> */}
<div className="settings-card"> {/* <div className="settings-card"> */}
{/* Passcode Lock — disabled for now, toggle is non-functional */} {/* Passcode Lock — disabled for now, toggle is non-functional */}
<div className="settings-item" style={{ opacity: 0.5 }}> {/* <div className="settings-item" style={{ opacity: 0.5 }}>
<div className="settings-item-icon settings-item-icon-green"> <div className="settings-item-icon settings-item-icon-green">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect> <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
@@ -382,7 +383,7 @@ export default function SettingsPage() {
/> />
<span className="settings-toggle-slider" style={{ cursor: 'not-allowed' }}></span> <span className="settings-toggle-slider" style={{ cursor: 'not-allowed' }}></span>
</label> </label>
</div> </div> */}
{/* Face ID — commented out for future use */} {/* Face ID — commented out for future use */}
{/* {/*
@@ -413,8 +414,8 @@ export default function SettingsPage() {
</label> </label>
</div> </div>
*/} */}
</div> {/* </div> */}
</section> {/* </section> */}
{/* App */} {/* App */}
<section className="settings-section"> <section className="settings-section">
@@ -477,7 +478,7 @@ export default function SettingsPage() {
{/* Data & Look */} {/* Data & Look */}
<section className="settings-section"> <section className="settings-section">
<h3 className="settings-section-title">DATA & LOOK</h3> <h3 className="settings-section-title">CUSTOMIZATION</h3>
<div className="settings-card"> <div className="settings-card">
{/* Export Journal — commented out for future use */} {/* Export Journal — commented out for future use */}
{/* {/*
@@ -580,11 +581,11 @@ export default function SettingsPage() {
{/* Clear Data */} {/* Clear Data */}
<button type="button" className="settings-clear-btn" onClick={handleClearData}> <button type="button" className="settings-clear-btn" onClick={handleClearData}>
<span>Clear All Data</span>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="3 6 5 6 21 6"></polyline> <polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg> </svg>
<span>Clear All Data</span>
</button> </button>
{/* Sign Out */} {/* Sign Out */}
@@ -650,14 +651,14 @@ export default function SettingsPage() {
<div className="confirm-modal" onClick={(e) => e.stopPropagation()}> <div className="confirm-modal" onClick={(e) => e.stopPropagation()}>
<h3 className="edit-modal-title">Edit Profile</h3> <h3 className="edit-modal-title">Edit Profile</h3>
<label className="edit-modal-avatar" style={{ cursor: 'pointer' }}> <input
<input ref={fileInputRef}
ref={fileInputRef} type="file"
type="file" accept="image/*"
accept="image/*" style={{ display: 'none' }}
style={{ display: 'none' }} onChange={handlePhotoSelect}
onChange={handlePhotoSelect} />
/> <div className="edit-modal-avatar">
{editPhotoPreview ? ( {editPhotoPreview ? (
<img src={editPhotoPreview} alt="Preview" className="edit-modal-avatar-img" <img src={editPhotoPreview} alt="Preview" className="edit-modal-avatar-img"
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
@@ -667,22 +668,25 @@ export default function SettingsPage() {
{editName.charAt(0).toUpperCase() || 'U'} {editName.charAt(0).toUpperCase() || 'U'}
</div> </div>
)} )}
<div className="edit-modal-avatar-overlay"> </div>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <div className="edit-modal-photo-actions">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
<circle cx="12" cy="13" r="4" />
</svg>
</div>
</label>
{editPhotoPreview && (
<button <button
type="button" type="button"
className="edit-modal-remove-photo" className="edit-modal-change-photo"
onClick={() => setEditPhotoPreview(null)} onClick={() => fileInputRef.current?.click()}
> >
Remove photo Change photo
</button> </button>
)} {editPhotoPreview && (
<button
type="button"
className="edit-modal-remove-photo"
onClick={() => setEditPhotoPreview(null)}
>
Remove photo
</button>
)}
</div>
<label className="confirm-modal-label" style={{ marginTop: '1rem' }}>Display Name</label> <label className="confirm-modal-label" style={{ marginTop: '1rem' }}>Display Name</label>
<input <input