Skip to content

Commit e533f2c

Browse files
committed
few fixes
1 parent eaf840c commit e533f2c

File tree

7 files changed

+87
-24
lines changed

7 files changed

+87
-24
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @browserstack/ctooffice

package-lock.json

Lines changed: 6 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Navbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Link, useLocation } from 'react-router-dom';
44
import { useCart } from '../contexts/CartContext';
55
import { useUser } from '../contexts/UserContext';
66
import { getAssetPath } from '../lib/assetUtils';
7+
import { sanitizeAvatarUrl } from '../lib/avatar';
78

89
const Navbar: React.FC = () => {
910
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@@ -80,7 +81,7 @@ const Navbar: React.FC = () => {
8081
>
8182
{user ? (
8283
<>
83-
<img src={user.avatar} alt={user.name} className="w-7 h-7 rounded-full border border-gray-300 shrink-0" />
84+
<img src={sanitizeAvatarUrl(user.avatar)} alt={user.name} className="w-7 h-7 rounded-full border border-gray-300 shrink-0" />
8485
<span className="hidden md:inline lg:inline xl:inline 2xl:inline text-sm font-medium text-ellipsis overflow-hidden max-w-[80px] lg:max-w-[120px] xl:max-w-[160px] 2xl:max-w-[200px] ipad:hidden">{user.name || user.email}</span>
8586
</>
8687
) : (

src/contexts/UserContext.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
22
import { userProfiles } from '../data/userProfiles';
3+
import { sanitizeAvatarUrl } from '../lib/avatar';
34

45
export interface User {
56
id: number;
@@ -19,21 +20,38 @@ interface UserContextType {
1920
user: User | null;
2021
login: (userId: string, email: string) => Promise<boolean>;
2122
logout: () => void;
23+
updateUser: (updates: Partial<User>) => void;
2224
}
2325

2426
const UserContext = createContext<UserContextType | undefined>(undefined);
2527

28+
const sanitizeUser = (value: User): User => ({
29+
...value,
30+
avatar: sanitizeAvatarUrl(value.avatar),
31+
});
32+
2633
export const UserProvider = ({ children }: { children: ReactNode }) => {
2734
const [user, setUser] = useState<User | null>(null);
2835

2936
useEffect(() => {
3037
const stored = localStorage.getItem('user');
31-
if (stored) setUser(JSON.parse(stored));
38+
if (!stored) return;
39+
try {
40+
const parsed = JSON.parse(stored);
41+
setUser(sanitizeUser(parsed));
42+
} catch {
43+
localStorage.removeItem('user');
44+
}
3245
}, []);
3346

3447
useEffect(() => {
35-
if (user) localStorage.setItem('user', JSON.stringify(user));
36-
else localStorage.removeItem('user');
48+
// NOTE: This sandbox only stores mock profiles used for demos; no real PII is persisted.
49+
if (user) {
50+
const safeUser = sanitizeUser(user);
51+
localStorage.setItem('user', JSON.stringify(safeUser));
52+
} else {
53+
localStorage.removeItem('user');
54+
}
3755
}, [user]);
3856

3957
const login = async (userId: string, email: string) => {
@@ -42,7 +60,7 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
4260
p => String(p.userId) === String(userId) && p.email === email
4361
);
4462
if (profile) {
45-
setUser({
63+
setUser(sanitizeUser({
4664
id: profile.userId,
4765
email: profile.email,
4866
username: profile.email.split('@')[0],
@@ -54,16 +72,20 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
5472
gender: 'other',
5573
company: 'Demo Inc.',
5674
website: 'https://example.com'
57-
});
75+
}));
5876
return true;
5977
}
6078
return false;
6179
};
6280

6381
const logout = () => setUser(null);
6482

83+
const updateUser = (updates: Partial<User>) => {
84+
setUser(prev => (prev ? sanitizeUser({ ...prev, ...updates }) : prev));
85+
};
86+
6587
return (
66-
<UserContext.Provider value={{ user, login, logout }}>
88+
<UserContext.Provider value={{ user, login, logout, updateUser }}>
6789
{children}
6890
</UserContext.Provider>
6991
);

src/lib/avatar.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getAssetPath } from './assetUtils';
2+
3+
const DEFAULT_AVATAR = getAssetPath('/static/avatars/demo1.svg');
4+
5+
const HTTP_URL_REGEX = /^(https?:\/\/)[^\s]+$/i;
6+
const DATA_URI_REGEX = /^data:image\/(png|jpeg|jpg|gif|webp|svg\+xml);base64,[A-Za-z0-9+/=]+$/i;
7+
8+
export const sanitizeAvatarUrl = (avatar?: string | null): string => {
9+
if (!avatar || typeof avatar !== 'string') {
10+
return DEFAULT_AVATAR;
11+
}
12+
const trimmed = avatar.trim();
13+
14+
if (trimmed.startsWith('/')) {
15+
return trimmed;
16+
}
17+
18+
if (HTTP_URL_REGEX.test(trimmed) || DATA_URI_REGEX.test(trimmed)) {
19+
return trimmed;
20+
}
21+
22+
return DEFAULT_AVATAR;
23+
};

src/pages/Checkout.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,24 @@ const Checkout: React.FC = () => {
2626
);
2727
}
2828

29+
const buildUserSummary = () => {
30+
if (!user) return null;
31+
const { id, email, name, username } = user;
32+
return { id, email, name, username };
33+
};
34+
2935
const handlePlaceOrder = () => {
3036
const order = {
3137
id: Date.now().toString(),
3238
date: new Date().toLocaleString(),
3339
items: cart.items,
3440
total: cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
35-
user,
41+
user: buildUserSummary(),
3642
};
3743
// Use user id if available, else fallback to email
38-
const userKey = user.id ? `order-history-${user.id}` : `order-history-${user.email}`;
44+
const userKey = user?.id
45+
? `order-history-${user.id}`
46+
: `order-history-${user?.email ?? 'guest'}`;
3947
const prev = localStorage.getItem(userKey);
4048
const orders = prev ? JSON.parse(prev) : [];
4149
orders.push(order);

src/pages/Profile.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import React, { useImperativeHandle, useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import { useUser } from '../contexts/UserContext';
3-
import { useNavigate } from 'react-router-dom';
43
import EmptyState from '../components/ui/EmptyState';
54
import { Input } from '../components/ui/input';
65
import { Button } from '../components/ui/button';
6+
import { sanitizeAvatarUrl } from '../lib/avatar';
77

88
const Profile: React.FC = () => {
9-
const { user, login, logout } = useUser();
9+
const { user, login, logout, updateUser } = useUser();
1010
const [email, setEmail] = useState('');
1111
const [loading, setLoading] = useState(false);
1212
const [error, setError] = useState('');
@@ -19,7 +19,6 @@ const Profile: React.FC = () => {
1919
company: user?.company || '',
2020
website: user?.website || ''
2121
});
22-
const navigate = useNavigate();
2322

2423
const handleLogin = async (e: React.FormEvent) => {
2524
e.preventDefault();
@@ -35,12 +34,22 @@ const Profile: React.FC = () => {
3534
};
3635

3736
const handleSave = () => {
38-
// Update user context and localStorage
39-
Object.assign(user!, form);
40-
localStorage.setItem('user', JSON.stringify(user));
37+
if (!user) return;
38+
updateUser(form);
4139
setEditMode(false);
4240
};
4341

42+
useEffect(() => {
43+
setForm({
44+
phone: user?.phone || '',
45+
address: user?.address || '',
46+
birthdate: user?.birthdate || '',
47+
gender: user?.gender || '',
48+
company: user?.company || '',
49+
website: user?.website || ''
50+
});
51+
}, [user]);
52+
4453
if (!user) {
4554
return (
4655
<div className="min-h-screen flex flex-col items-center justify-center py-8">
@@ -70,7 +79,7 @@ const Profile: React.FC = () => {
7079
return (
7180
<div className="min-h-screen flex flex-col items-center justify-center py-8">
7281
<div className="bg-white rounded-lg shadow p-8 flex flex-col items-center">
73-
<img src={user.avatar} alt={user.name} className="w-24 h-24 rounded-full mb-4" />
82+
<img src={sanitizeAvatarUrl(user.avatar)} alt={user.name} className="w-24 h-24 rounded-full mb-4" />
7483
<h2 className="text-xl font-bold mb-2">{user.name}</h2>
7584
<div className="text-gray-600 mb-2">{user.email}</div>
7685
<div className="text-gray-600 mb-2">@{user.username}</div>

0 commit comments

Comments
 (0)