Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"@sentry/sveltekit": "^10.40.0",
"axios": "^1.13.6",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"libphonenumber-js": "^1.12.38",
"svelte-dnd-action": "^0.9.69",
"tailwind-merge": "^3.5.0",
Expand Down
8 changes: 0 additions & 8 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/src/hooks.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = Buffer.from(base64, 'base64').toString('utf8');
return /** @type {JWTPayload} */ (JSON.parse(jsonPayload));
} catch (error) {

Check warning on line 44 in frontend/src/hooks.server.js

View workflow job for this annotation

GitHub Actions / frontend-checks

'error' is defined but never used
return null;
}
}
Expand Down Expand Up @@ -231,7 +231,7 @@
} else {
// Org switch failed, clear org cookie and redirect
event.cookies.delete('org', { path: '/' });
throw redirect(307, '/org');
throw redirect(303, '/org');
}
}
}
Expand Down
33 changes: 27 additions & 6 deletions frontend/src/lib/utils/formatting.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,33 @@
* @module lib/utils/formatting
*/

import { format, formatDistanceToNow } from 'date-fns';
const dateFormatter = new Intl.DateTimeFormat('en-US', {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).

month: 'short',
day: 'numeric',
year: 'numeric'
});

const relativeFormatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).

Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dateFormatter is configured with locale en-US, but relativeFormatter uses en. If the intent is consistent formatting across the app, consider using the same locale (or deriving both from a single locale setting) to avoid mismatched language/region output.

Suggested change
const relativeFormatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const relativeFormatter = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });

Copilot uses AI. Check for mistakes.

const RELATIVE_UNITS = /** @type {const} */ ([
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).

{ unit: 'year', ms: 365.25 * 24 * 60 * 60 * 1000 },
{ unit: 'month', ms: 30.44 * 24 * 60 * 60 * 1000 },
{ unit: 'week', ms: 7 * 24 * 60 * 60 * 1000 },
{ unit: 'day', ms: 24 * 60 * 60 * 1000 },
{ unit: 'hour', ms: 60 * 60 * 1000 },
{ unit: 'minute', ms: 60 * 1000 },
{ unit: 'second', ms: 1000 }
]);

/**
* Format a date string to a human-readable format
* Format a date string to a human-readable format (e.g., "Feb 18, 2026")
* @param {string | Date | null | undefined} date - Date to format
* @param {string} [formatStr='MMM d, yyyy'] - date-fns format string
* @returns {string} Formatted date string or '-' if no date
*/
export function formatDate(date, formatStr = 'MMM d, yyyy') {
export function formatDate(date) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'export' is only available in ES6 (use 'esversion: 6').

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'export' is only available in ES6 (use 'esversion: 6').

if (!date) return '-';
try {
return format(new Date(date), formatStr);
return dateFormatter.format(new Date(date));
} catch {
return '-';
}
Expand All @@ -28,7 +43,13 @@ export function formatDate(date, formatStr = 'MMM d, yyyy') {
export function formatRelativeDate(date) {
if (!date) return '-';
try {
return formatDistanceToNow(new Date(date), { addSuffix: true });
const diff = new Date(date).getTime() - Date.now();
for (const { unit, ms } of RELATIVE_UNITS) {
if (Math.abs(diff) >= ms || unit === 'second') {
return relativeFormatter.format(Math.round(diff / ms), unit);
}
}
return '-';
} catch {
return '-';
}
Expand Down
25 changes: 18 additions & 7 deletions frontend/src/lib/utils/phone.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { isValidPhoneNumber, parsePhoneNumber, isPossiblePhoneNumber } from 'libphonenumber-js';
/** @type {Promise<typeof import('libphonenumber-js')> | null} */
let _lib = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'let' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).


function getLib() {
if (!_lib) _lib = import('libphonenumber-js');
return _lib;
}

/**
* Validates a phone number and returns validation result
* @param {string} phoneNumber - The phone number to validate
* @param {string} [defaultCountry] - Default country code (e.g., 'US', 'IN')
* @returns {{ isValid: boolean, formatted?: string, error?: string }}
* @returns {Promise<{ isValid: boolean, formatted?: string, error?: string }>}
*/
export function validatePhoneNumber(phoneNumber, defaultCountry) {
export async function validatePhoneNumber(phoneNumber, defaultCountry) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'export' is only available in ES6 (use 'esversion: 6').
Expected an assignment or function call and instead saw an expression.
Missing semicolon.
Unexpected 'async'.

if (!phoneNumber || phoneNumber.trim() === '') {
return { isValid: true }; // Allow empty phone numbers
}

const trimmed = phoneNumber.trim();

try {
const { isValidPhoneNumber, parsePhoneNumber, isPossiblePhoneNumber } = await getLib();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
'destructuring binding' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
Missing semicolon.


// If the number starts with +, validate as international
if (trimmed.startsWith('+')) {
const isValid = isValidPhoneNumber(trimmed);
Expand Down Expand Up @@ -88,12 +96,13 @@ export function validatePhoneNumber(phoneNumber, defaultCountry) {
* Formats a phone number for display
* @param {string} phoneNumber - The phone number to format
* @param {string} defaultCountry - Default country code
* @returns {string} Formatted phone number or original if invalid
* @returns {Promise<string>} Formatted phone number or original if invalid
*/
export function formatPhoneNumber(phoneNumber, defaultCountry = 'US') {
export async function formatPhoneNumber(phoneNumber, defaultCountry = 'US') {
if (!phoneNumber) return '';

try {
const { parsePhoneNumber } = await getLib();
// @ts-ignore - defaultCountry is a valid CountryCode
const parsed = parsePhoneNumber(phoneNumber, { defaultCountry });
return parsed.formatInternational();
Expand All @@ -106,14 +115,16 @@ export function formatPhoneNumber(phoneNumber, defaultCountry = 'US') {
* Formats a phone number for storage (E.164 format)
* @param {string} phoneNumber - The phone number to format
* @param {string} [defaultCountry] - Default country code
* @returns {string} E.164 formatted phone number or original if invalid
* @returns {Promise<string>} E.164 formatted phone number or original if invalid
*/
export function formatPhoneForStorage(phoneNumber, defaultCountry) {
export async function formatPhoneForStorage(phoneNumber, defaultCountry) {
if (!phoneNumber) return '';

const trimmed = phoneNumber.trim();

try {
const { parsePhoneNumber } = await getLib();

// If starts with +, parse as international
if (trimmed.startsWith('+')) {
const parsed = parsePhoneNumber(trimmed);
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/routes/(app)/profile/+page.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ export const actions = {
// Validate phone if provided
let formattedPhone = null;
if (phone && phone.trim().length > 0) {
const phoneValidation = validatePhoneNumber(phone.trim());
const phoneValidation = await validatePhoneNumber(phone.trim());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'const' is available in ES6 (use 'esversion: 6') or Mozilla JS extensions (use moz).
Missing semicolon.

if (!phoneValidation.isValid) {
errors.phone = phoneValidation.error || 'Please enter a valid phone number';
} else {
formattedPhone = formatPhoneForStorage(phone.trim());
formattedPhone = await formatPhoneForStorage(phone.trim());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon.

}
}

Expand Down
16 changes: 13 additions & 3 deletions frontend/src/routes/(app)/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
let isEditing = $state(false);
let isSubmitting = $state(false);
let phoneError = $state('');
let formattedDisplayPhone = $state('');

// Form data state - initialized by $effect below
let formData = $state({
Expand All @@ -36,14 +37,23 @@
}
});

// Format phone for display (async, resolved into state)
$effect(() => {
if (data.user.phone) {
formatPhoneNumber(data.user.phone).then((f) => (formattedDisplayPhone = f));
} else {
formattedDisplayPhone = '';
}
});
Comment on lines +40 to +47
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async $effect that formats data.user.phone can race if data.user.phone changes before the previous formatPhoneNumber() promise resolves (stale promise may overwrite formattedDisplayPhone). Consider guarding with a monotonic request id / captured value check (only assign if the phone value is still current), or using an abortable pattern in the effect cleanup.

Copilot uses AI. Check for mistakes.

// Validate phone number on input
function validatePhone() {
async function validatePhone() {
if (!formData.phone.trim()) {
phoneError = '';
return;
}

const validation = validatePhoneNumber(formData.phone);
const validation = await validatePhoneNumber(formData.phone);
if (!validation.isValid) {
phoneError = validation.error || 'Invalid phone number';
} else {
Expand Down Expand Up @@ -277,7 +287,7 @@
Phone Number
</div>
<p class="text-foreground">
{data.user.phone ? formatPhoneNumber(data.user.phone) : 'Not provided'}
{formattedDisplayPhone || data.user.phone || 'Not provided'}
</p>
</div>

Expand Down
Loading