Skip to content
Open
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
24 changes: 12 additions & 12 deletions client-side/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
"@mui/x-charts": "^7.10.0",
"@mui/x-data-grid": "^7.10.0",
"@mui/x-date-pickers": "^7.11.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@popperjs/core": "^2.11.8",
"@reduxjs/toolkit": "^2.2.7",
"ajv": "^8.17.1",
"ajv-keywords": "^5.1.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@testing-library/user-event": "^13.5.0",
"@types/redux": "^3.6.31",
"ajv": "^8.17.1",
"ajv-keywords": "^5.1.0",
"axios": "^1.7.2",
"dayjs": "^1.11.12",
"dotenv": "^16.4.5",
Expand All @@ -39,8 +39,8 @@
"react-google-recaptcha": "^3.1.0",
"react-i18next": "^15.0.1",
"react-redux": "^9.1.2",
"react-router": "^6.24.1",
"react-refresh": "^0.14.2",
"react-router": "^6.24.1",
"react-router-dom": "^6.26.0",
"redux": "^5.0.1",
"styled-components": "^6.1.11",
Expand All @@ -49,10 +49,13 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/preset-env": "^7.25.3",
"@babel/preset-react": "^7.24.7",
"@chromatic-com/storybook": "^1.6.0",
"@eslint/compat": "^1.1.1",
"@eslint/js": "^9.6.0",
"@storybook/addon-actions": "^8.1.11",
"@storybook/addon-essentials": "^8.1.11",
"@storybook/addon-interactions": "^8.1.11",
Expand All @@ -73,20 +76,17 @@
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-storybook": "^0.8.0",
"globals": "^15.9.0",
"install": "^0.13.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"notistack": "^3.0.1",
"npm": "^10.8.2",
"prop-types": "^15.8.1",
"react-scripts": "^5.0.1",
"redux-mock-store": "^1.5.4",
"sass": "^1.77.6",
"webpack": "^5.92.1",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@chromatic-com/storybook": "^1.6.0",
"@eslint/js": "^9.6.0",
"install": "^0.13.0",
"npm": "^10.8.2",
"react-scripts": "^5.0.1",
"storybook": "^8.1.11"
"storybook": "^8.1.11",
"webpack": "^5.92.1"
},
"scripts": {
"start": "react-scripts start",
Expand Down
41 changes: 21 additions & 20 deletions client-side/src/components/settings/Notifications.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const Notifications = ({ onUpdate, data }) => {
const { t: translate } = useTranslation();
const { user } = useSelector(selectAuth);

const notificationTime = (data?.sendNotificationTime || user.preference?.sendNotificationTime || 10);
const initialSoundVoice = (data?.soundVoice || user.preference?.soundVoice || "alertSound.mp3");
const showIncomeMessages = (data?.displayIncomeMessages || user.preference?.displayIncomeMessages || false);
const showBrowsingTimeLimit = (data?.displayBrowsingTimeLimit || user.preference?.displayBrowsingTimeLimit || false);
const initialEmailFrequency = (data?.emailFrequency || user.preference?.emailFrequency || EMAIL_FREQUENCY_ENUM.NEVER);
const notificationTime = (data?.sendNotificationTime || user?.preference?.sendNotificationTime || 10);
const initialSoundVoice = (data?.soundVoice || user?.preference?.soundVoice || "alertSound.mp3");
const showIncomeMessages = (data?.displayIncomeMessages || user?.preference?.displayIncomeMessages || false);
const showBrowsingTimeLimit = (data?.displayBrowsingTimeLimit || user?.preference?.displayBrowsingTimeLimit || false);
const initialEmailFrequency = (data?.emailFrequency || user?.preference?.emailFrequency || EMAIL_FREQUENCY_ENUM.NEVER);

const [emailFrequency, setEmailFrequency] = useState(initialEmailFrequency);
const [ringtoneFile, setRingtoneFile] = useState({ name: initialSoundVoice });
Expand All @@ -30,15 +30,13 @@ const Notifications = ({ onUpdate, data }) => {
const [displayBrowsingTimeLimit, setDisplayBrowsingTimeLimit] = useState(showBrowsingTimeLimit);

const [prevValues] = useState({
emailFrequency: user.preference.emailFrequency,
ringtoneFile: { name: user.preference.soundVoice },
sendNotificationTime: user.preference.sendNotificationTime,
displayIncomeMessages: user.preference.displayIncomeMessages,
displayBrowsingTimeLimit: user.preference.displayBrowsingTimeLimit
emailFrequency: user?.preference.emailFrequency,
ringtoneFile: { name: user?.preference.soundVoice },
sendNotificationTime: user?.preference.sendNotificationTime,
displayIncomeMessages: user?.preference.displayIncomeMessages,
displayBrowsingTimeLimit: user?.preference.displayBrowsingTimeLimit
});



const emailFrequencyOptions = Object.keys(EMAIL_FREQUENCY_ENUM).map(key => ({
text: translate(key.toLowerCase()),
value: EMAIL_FREQUENCY_ENUM[key]
Expand All @@ -47,15 +45,15 @@ const Notifications = ({ onUpdate, data }) => {
useEffect(() => {
if (prevValues.emailFrequency !== emailFrequency) {
onUpdate({ emailFrequency });
}else {
} else if (data && 'emailFrequency' in data) {
onUpdate({ emailFrequency: undefined });
}
}, [emailFrequency]);

useEffect(() => {
if (prevValues.sendNotificationTime != sendNotificationTime) {
onUpdate({ sendNotificationTime });
}else {
} else if (data && 'sendNotificationTime' in data) {
onUpdate({ sendNotificationTime: undefined });
}
}, [sendNotificationTime]);
Expand All @@ -64,15 +62,15 @@ const Notifications = ({ onUpdate, data }) => {
if (prevValues.displayIncomeMessages !== displayIncomeMessages) {
onUpdate({ displayIncomeMessages });
}
else {
else if (data && 'displayIncomeMessages' in data) {
onUpdate({ displayIncomeMessages: undefined });
}
}, [displayIncomeMessages]);

useEffect(() => {
if (prevValues.displayBrowsingTimeLimit !== displayBrowsingTimeLimit) {
onUpdate({ displayBrowsingTimeLimit });
}else {
} else if (data && 'displayBrowsingTimeLimit' in data) {
onUpdate({ displayBrowsingTimeLimit: undefined });
}
}, [displayBrowsingTimeLimit]);
Expand All @@ -87,7 +85,7 @@ const Notifications = ({ onUpdate, data }) => {
audioElement.load();
}
return () => URL.revokeObjectURL(newSoundVoice);
}else {
} else if (data && 'soundVoice' in data) {
onUpdate({ soundVoice: undefined });
}
}, [ringtoneFile]);
Expand Down Expand Up @@ -136,6 +134,7 @@ const Notifications = ({ onUpdate, data }) => {
value={emailFrequency}
size='large'
widthOfSelect='11rem'
data-testid="select-email-frequency"
/>
</div>
<div className="input-container">
Expand All @@ -160,9 +159,11 @@ const Notifications = ({ onUpdate, data }) => {
accept='audio/mp3'
className='generic-input-file'
/>
<audio controls className="audio-player">
<source src={soundVoice} />
</audio>
{soundVoice &&
(<audio controls className="audio-player">
<source src={soundVoice} />
</audio>
)}
</div>
</div>
);
Expand Down
37 changes: 20 additions & 17 deletions client-side/src/components/settings/Preferences.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import CONSTANTS from './constantSetting.js';
import './Preferences.scss';

const createTimeZones = () => {
return moment.tz.names().map(timezone => ({
value: timezone,
text: timezone,
}));
return moment.tz.names()
.filter(timezone => timezone.startsWith('Etc'))
.map(timezone => ({
value: timezone.replace('Etc/', ''),
text: timezone.replace('Etc/', ''),
}));
};

const Preferences = ({ onUpdate, data }) => {
Expand All @@ -27,11 +29,11 @@ const Preferences = ({ onUpdate, data }) => {
text: LANGUAGE[key]['text'],
iconSrc: LANGUAGE[key]['icon']
}));
const initialLanguage = (data?.language || user.preference?.language || languageOptions[0].value);
const initialLanguage = (data?.language || user?.preference?.language || languageOptions[1].value);
const timeZoneOptions = createTimeZones();
const defaultTimeZone = timeZoneOptions.find(option => option.value === "UTC")?.value || timeZoneOptions[0].value;
const initialTimeZone = (data?.timeZone || user.preference?.timeZone || defaultTimeZone);
const initialDateFormat = (data?.dateFormat || user.preference?.dateFormat || DATE_FORMATS[0].value);
const initialTimeZone = (data?.timeZone || user?.preference?.timeZone || defaultTimeZone);
const initialDateFormat = (data?.dateFormat || user?.preference?.dateFormat || DATE_FORMATS[0].value);
const [language, setLanguage] = useState(initialLanguage);
const [timeZone, setTimeZone] = useState(initialTimeZone);
const [dateFormat, setDateFormat] = useState(initialDateFormat);
Expand All @@ -41,37 +43,35 @@ const Preferences = ({ onUpdate, data }) => {
value: value
}));

const [prevValues, setPrevValues] = useState({
language,
timeZone,
dateFormat
const [prevValues] = useState({
language:user?.preference.language,
timeZone:user?.preference.timeZone,
dateFormat:user?.preference.dateFormat
});

useEffect(() => {
if (prevValues.language !== language) {
onUpdate({ language });
setPrevValues(prev => ({ ...prev, language }));
}
else {
else if(data && 'language' in data){
onUpdate({ language: undefined });
}
}, [language]);

useEffect(() => {
if (prevValues.timeZone !== timeZone) {
onUpdate({ timeZone });
setPrevValues(prev => ({ ...prev, timeZone }));
}else {
}else if(data && 'timeZone' in data){
onUpdate({ timeZone: undefined });
}
}, [timeZone]);

useEffect(() => {
if (prevValues.dateFormat !== dateFormat) {
onUpdate({ dateFormat });
setPrevValues(prev => ({ ...prev, dateFormat }));
}else {
}else if(data && 'dateFormat' in data){
onUpdate({ dateFormat: undefined });

}
}, [dateFormat]);

Expand All @@ -95,6 +95,7 @@ const Preferences = ({ onUpdate, data }) => {
widthOfSelect='11rem'
value={language}
onChange={handleLanguageChange}
data-testid="select-language"
/>
</div>
<div className="div-select">
Expand All @@ -106,6 +107,7 @@ const Preferences = ({ onUpdate, data }) => {
value={timeZone}
size='large'
widthOfSelect='11rem'
data-testid="select-time-zone"
/>
</div>
<div className="div-select">
Expand All @@ -117,6 +119,7 @@ const Preferences = ({ onUpdate, data }) => {
title={translate(LABELS.SELECT_DATE_FORMAT)}
size='large'
widthOfSelect='11rem'
data-testid="select-date-format"
/>
</div>
</div>
Expand Down
10 changes: 3 additions & 7 deletions client-side/src/components/settings/Settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ import { useSelector, useDispatch } from 'react-redux';
import VerticalTabs from '../../stories/verticalTabs/verticalTabss';
import GenericButton from '../../stories/Button/GenericButton.jsx';
import ToastMessage from '../../stories/Toast/ToastMessage.jsx';
import { updatePreference as updatePreferenceService } from '../../services/preferenceService.js';
import { updatePreference } from '../../redux/preference/preference.slice.js';
import { updatePreference } from '../../services/preferenceService.js';
import { updateCurrentUser } from '../../redux/auth/auth.slice.js';
import { selectAuth } from '../../redux/auth/auth.selector.js';
import { selectPreference } from '../../redux/preference/preference.selector.js';

import Preferences from './Preferences.jsx';
import AccountTab from './AccountTab.jsx';
Expand All @@ -24,13 +22,12 @@ const Settings = () => {
const { t: translate } = useTranslation();
const { enqueueSnackbar } = useSnackbar();
const { user } = useSelector(selectAuth);
const updatedPreferences = useSelector(selectPreference);
const dispatch = useDispatch();

const [notificationsData, setNotificationsData] = useState({});
const [preferencesData, setPreferencesData] = useState({});

const preferenceId = user.preference._id;
const preferenceId = user?.preference._id;

const handleUpdatePreferences = (updatedPreferences) => {
setPreferencesData(prev => {
Expand Down Expand Up @@ -63,10 +60,9 @@ const Settings = () => {
if (value !== undefined) formData.append(key, value);
});
try {
const response = await updatePreferenceService(preferenceId, formData);
const response = await updatePreference(preferenceId, formData);
if (response) {
enqueueSnackbar(<ToastMessage message={translate(MESSAGES.SUCCESS_UPDATED_SETTINGS)} type="success" />);
dispatch(updatePreference(response));
dispatch(updateCurrentUser({
...user,
preference: response
Expand Down
82 changes: 82 additions & 0 deletions client-side/src/components/settings/UserLocalization.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';

const Localization = ({ preferenceId }) => {
const [timeZone, setTimeZone] = useState('UTC');
const [language, setLanguage] = useState('en');

const baseUrl = process.env.REACT_APP_BASE_URL;
const permissionRef = useRef(false);

useEffect(() => {

const askForPermission = async () => {
console.log('askForPermission executed');
const permission = window.confirm('Allow us to get your location and preferred language for a better experience?');
permissionRef.current = true;

if (permission) {
// Get time zone
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimeZone(userTimeZone);

// Get language
const userLanguage = navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language || 'en';
setLanguage(userLanguage);

// Update preferences with user's data
updateUserPreferences(userTimeZone, userLanguage);
},
(error) => {
console.error('Error getting location:', error);
// Since geolocation failed, update with defaults
updateUserPreferences(timeZone, language);
}
);
} else {
console.error('Geolocation is not supported by this browser.');
// Update with defaults if geolocation not supported
updateUserPreferences(timeZone, language);
}
} else {
// Permission denied, update with defaults
updateUserPreferences(timeZone, language);
}
};

// Only ask for permission if it hasn't been granted before
if (permissionRef.current == false) {
askForPermission();
}
}, []);

const updateUserPreferences = async (updatedTimeZone, updatedLanguage) => {
try {
const formData = new FormData();
formData.append('timeZone', updatedTimeZone);
formData.append('language', updatedLanguage);

const response = await axios.put(`${baseUrl}/preferences/${preferenceId}`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
console.log(response.data);
} catch (error) {
console.error('Error updating preferences:', error);
}
};

return (
<div>
<h1>Localization Settings</h1>
<p>Time Zone: {timeZone}</p>
<p>Preferred Language: {language}</p>
</div>
);
};

export default Localization;
Empty file.
Loading