diff --git a/package.json b/package.json
index df96524779..048fce7c0d 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,7 @@
"danger-plugin-todos": "^1.3.1",
"dataloader": "^2.2.2",
"date-fns": "^2.30.0",
+ "date-fns-tz": "^2.0.0",
"debounce": "^2.0.0",
"deep-equal": "^2.2.2",
"docusaurus-node-polyfills": "^1.0.0",
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarSettingsSection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarAccountsListCard.tsx
similarity index 62%
rename from packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarSettingsSection.tsx
rename to packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarAccountsListCard.tsx
index b06954f3cf..5425976316 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarSettingsSection.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarAccountsListCard.tsx
@@ -10,9 +10,7 @@ import { SettingsAccountsListCard } from '@/settings/accounts/components/Setting
import { SettingsAccountsSynchronizationStatus } from '@/settings/accounts/components/SettingsAccountsSynchronizationStatus';
import { IconChevronRight } from '@/ui/display/icon';
import { IconGoogleCalendar } from '@/ui/display/icon/components/IconGoogleCalendar';
-import { H2Title } from '@/ui/display/typography/components/H2Title';
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
-import { Section } from '@/ui/layout/section/components/Section';
import { mockedConnectedAccounts } from '~/testing/mock-data/accounts';
const StyledRowRightContainer = styled.div`
@@ -21,7 +19,7 @@ const StyledRowRightContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
-export const SettingsAccountsCalendarSettingsSection = () => {
+export const SettingsAccountsCalendarAccountsListCard = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const navigate = useNavigate();
@@ -35,25 +33,19 @@ export const SettingsAccountsCalendarSettingsSection = () => {
});
return (
-
-
-
- navigate(`/settings/accounts/calendars/${account.id}`)
- }
- RowIcon={IconGoogleCalendar}
- RowRightComponent={({ account: _account }) => (
-
-
-
-
- )}
- />
-
+
+ navigate(`/settings/accounts/calendars/${account.id}`)
+ }
+ RowIcon={IconGoogleCalendar}
+ RowRightComponent={({ account: _account }) => (
+
+
+
+
+ )}
+ />
);
};
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarDisplaySettings.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarDisplaySettings.tsx
new file mode 100644
index 0000000000..880cf68c04
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarDisplaySettings.tsx
@@ -0,0 +1,47 @@
+import { useState } from 'react';
+import styled from '@emotion/styled';
+import { formatInTimeZone } from 'date-fns-tz';
+
+import { SettingsAccountsCalendarTimeZoneSelect } from '@/settings/accounts/components/SettingsAccountsCalendarTimeZoneSelect';
+import { detectTimeZone } from '@/settings/accounts/utils/detectTimeZone';
+import { Select } from '@/ui/input/components/Select';
+
+const StyledContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.spacing(4)};
+`;
+
+export const SettingsAccountsCalendarDisplaySettings = () => {
+ // TODO: use the user's saved time zone. If undefined, default it with the user's detected time zone.
+ const [timeZone, setTimeZone] = useState(detectTimeZone());
+
+ // TODO: use the user's saved time format.
+ const [timeFormat, setTimeFormat] = useState<12 | 24>(24);
+
+ return (
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarTimeZoneSelect.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarTimeZoneSelect.tsx
new file mode 100644
index 0000000000..159f5e7625
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarTimeZoneSelect.tsx
@@ -0,0 +1,25 @@
+import { availableTimeZoneOptions } from '@/settings/accounts/constants/timeZoneSelectOptions';
+import { detectTimeZone } from '@/settings/accounts/utils/detectTimeZone';
+import { findAvailableTimeZoneOption } from '@/settings/accounts/utils/findAvailableTimeZoneOption';
+import { Select } from '@/ui/input/components/Select';
+
+type SettingsAccountsCalendarTimeZoneSelectProps = {
+ value?: string;
+ onChange: (nextValue: string) => void;
+};
+
+export const SettingsAccountsCalendarTimeZoneSelect = ({
+ value = detectTimeZone(),
+ onChange,
+}: SettingsAccountsCalendarTimeZoneSelectProps) => (
+
+);
diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsSyncSection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsAccountsListCard.tsx
similarity index 66%
rename from packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsSyncSection.tsx
rename to packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsAccountsListCard.tsx
index e6d3cfa86d..2fc6154f1e 100644
--- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsSyncSection.tsx
+++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsAccountsListCard.tsx
@@ -12,8 +12,6 @@ import { SettingsAccountsListCard } from '@/settings/accounts/components/Setting
import { SettingsAccountsSynchronizationStatus } from '@/settings/accounts/components/SettingsAccountsSynchronizationStatus';
import { IconChevronRight } from '@/ui/display/icon';
import { IconGmail } from '@/ui/display/icon/components/IconGmail';
-import { H2Title } from '@/ui/display/typography/components/H2Title';
-import { Section } from '@/ui/layout/section/components/Section';
const StyledRowRightContainer = styled.div`
align-items: center;
@@ -21,7 +19,7 @@ const StyledRowRightContainer = styled.div`
gap: ${({ theme }) => theme.spacing(1)};
`;
-export const SettingsAccountsEmailsSyncSection = () => {
+export const SettingsAccountsEmailsAccountsListCard = () => {
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const navigate = useNavigate();
@@ -53,27 +51,21 @@ export const SettingsAccountsEmailsSyncSection = () => {
);
return (
-
-
-
- navigate(`/settings/accounts/emails/${messageChannel.id}`)
- }
- RowIcon={IconGmail}
- RowRightComponent={({ account: messageChannel }) => (
-
-
-
-
- )}
- />
-
+
+ navigate(`/settings/accounts/emails/${messageChannel.id}`)
+ }
+ RowIcon={IconGmail}
+ RowRightComponent={({ account: messageChannel }) => (
+
+
+
+
+ )}
+ />
);
};
diff --git a/packages/twenty-front/src/modules/settings/accounts/constants/ianaTimeZones.ts b/packages/twenty-front/src/modules/settings/accounts/constants/ianaTimeZones.ts
new file mode 100644
index 0000000000..2b5c179014
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/constants/ianaTimeZones.ts
@@ -0,0 +1,601 @@
+/**
+ * Standard IANA time zones.
+ * @see https://www.iana.org/time-zones
+ */
+export const ianaTimeZones = [
+ 'Africa/Abidjan',
+ 'Africa/Accra',
+ 'Africa/Addis_Ababa',
+ 'Africa/Algiers',
+ 'Africa/Asmara',
+ 'Africa/Asmera',
+ 'Africa/Bamako',
+ 'Africa/Bangui',
+ 'Africa/Banjul',
+ 'Africa/Bissau',
+ 'Africa/Blantyre',
+ 'Africa/Brazzaville',
+ 'Africa/Bujumbura',
+ 'Africa/Cairo',
+ 'Africa/Casablanca',
+ 'Africa/Ceuta',
+ 'Africa/Conakry',
+ 'Africa/Dakar',
+ 'Africa/Dar_es_Salaam',
+ 'Africa/Djibouti',
+ 'Africa/Douala',
+ 'Africa/El_Aaiun',
+ 'Africa/Freetown',
+ 'Africa/Gaborone',
+ 'Africa/Harare',
+ 'Africa/Johannesburg',
+ 'Africa/Juba',
+ 'Africa/Kampala',
+ 'Africa/Khartoum',
+ 'Africa/Kigali',
+ 'Africa/Kinshasa',
+ 'Africa/Lagos',
+ 'Africa/Libreville',
+ 'Africa/Lome',
+ 'Africa/Luanda',
+ 'Africa/Lubumbashi',
+ 'Africa/Lusaka',
+ 'Africa/Malabo',
+ 'Africa/Maputo',
+ 'Africa/Maseru',
+ 'Africa/Mbabane',
+ 'Africa/Mogadishu',
+ 'Africa/Monrovia',
+ 'Africa/Nairobi',
+ 'Africa/Ndjamena',
+ 'Africa/Niamey',
+ 'Africa/Nouakchott',
+ 'Africa/Ouagadougou',
+ 'Africa/Porto-Novo',
+ 'Africa/Sao_Tome',
+ 'Africa/Timbuktu',
+ 'Africa/Tripoli',
+ 'Africa/Tunis',
+ 'Africa/Windhoek',
+ 'America/Adak',
+ 'America/Anchorage',
+ 'America/Anguilla',
+ 'America/Antigua',
+ 'America/Araguaina',
+ 'America/Argentina/Buenos_Aires',
+ 'America/Argentina/Catamarca',
+ 'America/Argentina/ComodRivadavia',
+ 'America/Argentina/Cordoba',
+ 'America/Argentina/Jujuy',
+ 'America/Argentina/La_Rioja',
+ 'America/Argentina/Mendoza',
+ 'America/Argentina/Rio_Gallegos',
+ 'America/Argentina/Salta',
+ 'America/Argentina/San_Juan',
+ 'America/Argentina/San_Luis',
+ 'America/Argentina/Tucuman',
+ 'America/Argentina/Ushuaia',
+ 'America/Aruba',
+ 'America/Asuncion',
+ 'America/Atikokan',
+ 'America/Atka',
+ 'America/Bahia',
+ 'America/Bahia_Banderas',
+ 'America/Barbados',
+ 'America/Belem',
+ 'America/Belize',
+ 'America/Blanc-Sablon',
+ 'America/Boa_Vista',
+ 'America/Bogota',
+ 'America/Boise',
+ 'America/Buenos_Aires',
+ 'America/Cambridge_Bay',
+ 'America/Campo_Grande',
+ 'America/Cancun',
+ 'America/Caracas',
+ 'America/Catamarca',
+ 'America/Cayenne',
+ 'America/Cayman',
+ 'America/Chicago',
+ 'America/Chihuahua',
+ 'America/Coral_Harbour',
+ 'America/Cordoba',
+ 'America/Costa_Rica',
+ 'America/Creston',
+ 'America/Cuiaba',
+ 'America/Curacao',
+ 'America/Danmarkshavn',
+ 'America/Dawson',
+ 'America/Dawson_Creek',
+ 'America/Denver',
+ 'America/Detroit',
+ 'America/Dominica',
+ 'America/Edmonton',
+ 'America/Eirunepe',
+ 'America/El_Salvador',
+ 'America/Ensenada',
+ 'America/Fort_Nelson',
+ 'America/Fort_Wayne',
+ 'America/Fortaleza',
+ 'America/Glace_Bay',
+ 'America/Godthab',
+ 'America/Goose_Bay',
+ 'America/Grand_Turk',
+ 'America/Grenada',
+ 'America/Guadeloupe',
+ 'America/Guatemala',
+ 'America/Guayaquil',
+ 'America/Guyana',
+ 'America/Halifax',
+ 'America/Havana',
+ 'America/Hermosillo',
+ 'America/Indiana/Indianapolis',
+ 'America/Indiana/Knox',
+ 'America/Indiana/Marengo',
+ 'America/Indiana/Petersburg',
+ 'America/Indiana/Tell_City',
+ 'America/Indiana/Vevay',
+ 'America/Indiana/Vincennes',
+ 'America/Indiana/Winamac',
+ 'America/Indianapolis',
+ 'America/Inuvik',
+ 'America/Iqaluit',
+ 'America/Jamaica',
+ 'America/Jujuy',
+ 'America/Juneau',
+ 'America/Kentucky/Louisville',
+ 'America/Kentucky/Monticello',
+ 'America/Knox_IN',
+ 'America/Kralendijk',
+ 'America/La_Paz',
+ 'America/Lima',
+ 'America/Los_Angeles',
+ 'America/Louisville',
+ 'America/Lower_Princes',
+ 'America/Maceio',
+ 'America/Managua',
+ 'America/Manaus',
+ 'America/Marigot',
+ 'America/Martinique',
+ 'America/Matamoros',
+ 'America/Mazatlan',
+ 'America/Mendoza',
+ 'America/Menominee',
+ 'America/Merida',
+ 'America/Metlakatla',
+ 'America/Mexico_City',
+ 'America/Miquelon',
+ 'America/Moncton',
+ 'America/Monterrey',
+ 'America/Montevideo',
+ 'America/Montreal',
+ 'America/Montserrat',
+ 'America/Nassau',
+ 'America/New_York',
+ 'America/Nipigon',
+ 'America/Nome',
+ 'America/Noronha',
+ 'America/North_Dakota/Beulah',
+ 'America/North_Dakota/Center',
+ 'America/North_Dakota/New_Salem',
+ 'America/Nuuk',
+ 'America/Ojinaga',
+ 'America/Panama',
+ 'America/Pangnirtung',
+ 'America/Paramaribo',
+ 'America/Phoenix',
+ 'America/Port_of_Spain',
+ 'America/Port-au-Prince',
+ 'America/Porto_Acre',
+ 'America/Porto_Velho',
+ 'America/Puerto_Rico',
+ 'America/Punta_Arenas',
+ 'America/Rainy_River',
+ 'America/Rankin_Inlet',
+ 'America/Recife',
+ 'America/Regina',
+ 'America/Resolute',
+ 'America/Rio_Branco',
+ 'America/Rosario',
+ 'America/Santa_Isabel',
+ 'America/Santarem',
+ 'America/Santiago',
+ 'America/Santo_Domingo',
+ 'America/Sao_Paulo',
+ 'America/Scoresbysund',
+ 'America/Shiprock',
+ 'America/Sitka',
+ 'America/St_Barthelemy',
+ 'America/St_Johns',
+ 'America/St_Kitts',
+ 'America/St_Lucia',
+ 'America/St_Thomas',
+ 'America/St_Vincent',
+ 'America/Swift_Current',
+ 'America/Tegucigalpa',
+ 'America/Thule',
+ 'America/Thunder_Bay',
+ 'America/Tijuana',
+ 'America/Toronto',
+ 'America/Tortola',
+ 'America/Vancouver',
+ 'America/Virgin',
+ 'America/Whitehorse',
+ 'America/Winnipeg',
+ 'America/Yakutat',
+ 'America/Yellowknife',
+ 'Antarctica/Casey',
+ 'Antarctica/Davis',
+ 'Antarctica/DumontDUrville',
+ 'Antarctica/Macquarie',
+ 'Antarctica/Mawson',
+ 'Antarctica/McMurdo',
+ 'Antarctica/Palmer',
+ 'Antarctica/Rothera',
+ 'Antarctica/South_Pole',
+ 'Antarctica/Syowa',
+ 'Antarctica/Troll',
+ 'Antarctica/Vostok',
+ 'Arctic/Longyearbyen',
+ 'Asia/Aden',
+ 'Asia/Almaty',
+ 'Asia/Amman',
+ 'Asia/Anadyr',
+ 'Asia/Aqtau',
+ 'Asia/Aqtobe',
+ 'Asia/Ashgabat',
+ 'Asia/Ashkhabad',
+ 'Asia/Atyrau',
+ 'Asia/Baghdad',
+ 'Asia/Bahrain',
+ 'Asia/Baku',
+ 'Asia/Bangkok',
+ 'Asia/Barnaul',
+ 'Asia/Beirut',
+ 'Asia/Bishkek',
+ 'Asia/Brunei',
+ 'Asia/Calcutta',
+ 'Asia/Chita',
+ 'Asia/Choibalsan',
+ 'Asia/Chongqing',
+ 'Asia/Chungking',
+ 'Asia/Colombo',
+ 'Asia/Dacca',
+ 'Asia/Damascus',
+ 'Asia/Dhaka',
+ 'Asia/Dili',
+ 'Asia/Dubai',
+ 'Asia/Dushanbe',
+ 'Asia/Famagusta',
+ 'Asia/Gaza',
+ 'Asia/Harbin',
+ 'Asia/Hebron',
+ 'Asia/Ho_Chi_Minh',
+ 'Asia/Hong_Kong',
+ 'Asia/Hovd',
+ 'Asia/Irkutsk',
+ 'Asia/Istanbul',
+ 'Asia/Jakarta',
+ 'Asia/Jayapura',
+ 'Asia/Jerusalem',
+ 'Asia/Kabul',
+ 'Asia/Kamchatka',
+ 'Asia/Karachi',
+ 'Asia/Kashgar',
+ 'Asia/Kathmandu',
+ 'Asia/Katmandu',
+ 'Asia/Khandyga',
+ 'Asia/Kolkata',
+ 'Asia/Krasnoyarsk',
+ 'Asia/Kuala_Lumpur',
+ 'Asia/Kuching',
+ 'Asia/Kuwait',
+ 'Asia/Macao',
+ 'Asia/Macau',
+ 'Asia/Magadan',
+ 'Asia/Makassar',
+ 'Asia/Manila',
+ 'Asia/Muscat',
+ 'Asia/Nicosia',
+ 'Asia/Novokuznetsk',
+ 'Asia/Novosibirsk',
+ 'Asia/Omsk',
+ 'Asia/Oral',
+ 'Asia/Phnom_Penh',
+ 'Asia/Pontianak',
+ 'Asia/Pyongyang',
+ 'Asia/Qatar',
+ 'Asia/Qostanay',
+ 'Asia/Qyzylorda',
+ 'Asia/Rangoon',
+ 'Asia/Riyadh',
+ 'Asia/Saigon',
+ 'Asia/Sakhalin',
+ 'Asia/Samarkand',
+ 'Asia/Seoul',
+ 'Asia/Shanghai',
+ 'Asia/Singapore',
+ 'Asia/Srednekolymsk',
+ 'Asia/Taipei',
+ 'Asia/Tashkent',
+ 'Asia/Tbilisi',
+ 'Asia/Tehran',
+ 'Asia/Tel_Aviv',
+ 'Asia/Thimbu',
+ 'Asia/Thimphu',
+ 'Asia/Tokyo',
+ 'Asia/Tomsk',
+ 'Asia/Ujung_Pandang',
+ 'Asia/Ulaanbaatar',
+ 'Asia/Ulan_Bator',
+ 'Asia/Urumqi',
+ 'Asia/Ust-Nera',
+ 'Asia/Vientiane',
+ 'Asia/Vladivostok',
+ 'Asia/Yakutsk',
+ 'Asia/Yangon',
+ 'Asia/Yekaterinburg',
+ 'Asia/Yerevan',
+ 'Atlantic/Azores',
+ 'Atlantic/Bermuda',
+ 'Atlantic/Canary',
+ 'Atlantic/Cape_Verde',
+ 'Atlantic/Faeroe',
+ 'Atlantic/Faroe',
+ 'Atlantic/Jan_Mayen',
+ 'Atlantic/Madeira',
+ 'Atlantic/Reykjavik',
+ 'Atlantic/South_Georgia',
+ 'Atlantic/St_Helena',
+ 'Atlantic/Stanley',
+ 'Australia/ACT',
+ 'Australia/Adelaide',
+ 'Australia/Brisbane',
+ 'Australia/Broken_Hill',
+ 'Australia/Canberra',
+ 'Australia/Currie',
+ 'Australia/Darwin',
+ 'Australia/Eucla',
+ 'Australia/Hobart',
+ 'Australia/LHI',
+ 'Australia/Lindeman',
+ 'Australia/Lord_Howe',
+ 'Australia/Melbourne',
+ 'Australia/North',
+ 'Australia/NSW',
+ 'Australia/Perth',
+ 'Australia/Queensland',
+ 'Australia/South',
+ 'Australia/Sydney',
+ 'Australia/Tasmania',
+ 'Australia/Victoria',
+ 'Australia/West',
+ 'Australia/Yancowinna',
+ 'Brazil/Acre',
+ 'Brazil/DeNoronha',
+ 'Brazil/East',
+ 'Brazil/West',
+ 'Canada/Atlantic',
+ 'Canada/Central',
+ 'Canada/Eastern',
+ 'Canada/Mountain',
+ 'Canada/Newfoundland',
+ 'Canada/Pacific',
+ 'Canada/Saskatchewan',
+ 'Canada/Yukon',
+ 'CET',
+ 'Chile/Continental',
+ 'Chile/EasterIsland',
+ 'CST6CDT',
+ 'Cuba',
+ 'EET',
+ 'Egypt',
+ 'Eire',
+ 'EST',
+ 'EST5EDT',
+ 'Etc/GMT',
+ 'Etc/GMT-0',
+ 'Etc/GMT-1',
+ 'Etc/GMT-10',
+ 'Etc/GMT-11',
+ 'Etc/GMT-12',
+ 'Etc/GMT-13',
+ 'Etc/GMT-14',
+ 'Etc/GMT-2',
+ 'Etc/GMT-3',
+ 'Etc/GMT-4',
+ 'Etc/GMT-5',
+ 'Etc/GMT-6',
+ 'Etc/GMT-7',
+ 'Etc/GMT-8',
+ 'Etc/GMT-9',
+ 'Etc/GMT+0',
+ 'Etc/GMT+1',
+ 'Etc/GMT+10',
+ 'Etc/GMT+11',
+ 'Etc/GMT+12',
+ 'Etc/GMT+2',
+ 'Etc/GMT+3',
+ 'Etc/GMT+4',
+ 'Etc/GMT+5',
+ 'Etc/GMT+6',
+ 'Etc/GMT+7',
+ 'Etc/GMT+8',
+ 'Etc/GMT+9',
+ 'Etc/GMT0',
+ 'Etc/Greenwich',
+ 'Etc/UCT',
+ 'Etc/Universal',
+ 'Etc/UTC',
+ 'Etc/Zulu',
+ 'Europe/Amsterdam',
+ 'Europe/Andorra',
+ 'Europe/Astrakhan',
+ 'Europe/Athens',
+ 'Europe/Belfast',
+ 'Europe/Belgrade',
+ 'Europe/Berlin',
+ 'Europe/Bratislava',
+ 'Europe/Brussels',
+ 'Europe/Bucharest',
+ 'Europe/Budapest',
+ 'Europe/Busingen',
+ 'Europe/Chisinau',
+ 'Europe/Copenhagen',
+ 'Europe/Dublin',
+ 'Europe/Gibraltar',
+ 'Europe/Guernsey',
+ 'Europe/Helsinki',
+ 'Europe/Isle_of_Man',
+ 'Europe/Istanbul',
+ 'Europe/Jersey',
+ 'Europe/Kaliningrad',
+ 'Europe/Kiev',
+ 'Europe/Kirov',
+ 'Europe/Kyiv',
+ 'Europe/Lisbon',
+ 'Europe/Ljubljana',
+ 'Europe/London',
+ 'Europe/Luxembourg',
+ 'Europe/Madrid',
+ 'Europe/Malta',
+ 'Europe/Mariehamn',
+ 'Europe/Minsk',
+ 'Europe/Monaco',
+ 'Europe/Moscow',
+ 'Europe/Nicosia',
+ 'Europe/Oslo',
+ 'Europe/Paris',
+ 'Europe/Podgorica',
+ 'Europe/Prague',
+ 'Europe/Riga',
+ 'Europe/Rome',
+ 'Europe/Samara',
+ 'Europe/San_Marino',
+ 'Europe/Sarajevo',
+ 'Europe/Saratov',
+ 'Europe/Simferopol',
+ 'Europe/Skopje',
+ 'Europe/Sofia',
+ 'Europe/Stockholm',
+ 'Europe/Tallinn',
+ 'Europe/Tirane',
+ 'Europe/Tiraspol',
+ 'Europe/Ulyanovsk',
+ 'Europe/Uzhgorod',
+ 'Europe/Vaduz',
+ 'Europe/Vatican',
+ 'Europe/Vienna',
+ 'Europe/Vilnius',
+ 'Europe/Volgograd',
+ 'Europe/Warsaw',
+ 'Europe/Zagreb',
+ 'Europe/Zaporozhye',
+ 'Europe/Zurich',
+ 'GB',
+ 'GB-Eire',
+ 'GMT',
+ 'GMT-0',
+ 'GMT+0',
+ 'GMT0',
+ 'Greenwich',
+ 'Hongkong',
+ 'HST',
+ 'Iceland',
+ 'Indian/Antananarivo',
+ 'Indian/Chagos',
+ 'Indian/Christmas',
+ 'Indian/Cocos',
+ 'Indian/Comoro',
+ 'Indian/Kerguelen',
+ 'Indian/Mahe',
+ 'Indian/Maldives',
+ 'Indian/Mauritius',
+ 'Indian/Mayotte',
+ 'Indian/Reunion',
+ 'Iran',
+ 'Israel',
+ 'Jamaica',
+ 'Japan',
+ 'Kwajalein',
+ 'Libya',
+ 'MET',
+ 'Mexico/BajaNorte',
+ 'Mexico/BajaSur',
+ 'Mexico/General',
+ 'MST',
+ 'MST7MDT',
+ 'Navajo',
+ 'NZ',
+ 'NZ-CHAT',
+ 'Pacific/Apia',
+ 'Pacific/Auckland',
+ 'Pacific/Bougainville',
+ 'Pacific/Chatham',
+ 'Pacific/Chuuk',
+ 'Pacific/Easter',
+ 'Pacific/Efate',
+ 'Pacific/Enderbury',
+ 'Pacific/Fakaofo',
+ 'Pacific/Fiji',
+ 'Pacific/Funafuti',
+ 'Pacific/Galapagos',
+ 'Pacific/Gambier',
+ 'Pacific/Guadalcanal',
+ 'Pacific/Guam',
+ 'Pacific/Honolulu',
+ 'Pacific/Johnston',
+ 'Pacific/Kanton',
+ 'Pacific/Kiritimati',
+ 'Pacific/Kosrae',
+ 'Pacific/Kwajalein',
+ 'Pacific/Majuro',
+ 'Pacific/Marquesas',
+ 'Pacific/Midway',
+ 'Pacific/Nauru',
+ 'Pacific/Niue',
+ 'Pacific/Norfolk',
+ 'Pacific/Noumea',
+ 'Pacific/Pago_Pago',
+ 'Pacific/Palau',
+ 'Pacific/Pitcairn',
+ 'Pacific/Pohnpei',
+ 'Pacific/Ponape',
+ 'Pacific/Port_Moresby',
+ 'Pacific/Rarotonga',
+ 'Pacific/Saipan',
+ 'Pacific/Samoa',
+ 'Pacific/Tahiti',
+ 'Pacific/Tarawa',
+ 'Pacific/Tongatapu',
+ 'Pacific/Truk',
+ 'Pacific/Wake',
+ 'Pacific/Wallis',
+ 'Pacific/Yap',
+ 'Poland',
+ 'Portugal',
+ 'PRC',
+ 'PST8PDT',
+ 'ROC',
+ 'ROK',
+ 'Singapore',
+ 'Turkey',
+ 'UCT',
+ 'Universal',
+ 'US/Alaska',
+ 'US/Aleutian',
+ 'US/Arizona',
+ 'US/Central',
+ 'US/East-Indiana',
+ 'US/Eastern',
+ 'US/Hawaii',
+ 'US/Indiana-Starke',
+ 'US/Michigan',
+ 'US/Mountain',
+ 'US/Pacific',
+ 'US/Samoa',
+ 'UTC',
+ 'W-SU',
+ 'WET',
+ 'Zulu',
+];
diff --git a/packages/twenty-front/src/modules/settings/accounts/constants/timeZoneSelectOptions.ts b/packages/twenty-front/src/modules/settings/accounts/constants/timeZoneSelectOptions.ts
new file mode 100644
index 0000000000..ffb3d5e67a
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/constants/timeZoneSelectOptions.ts
@@ -0,0 +1,43 @@
+import { getTimezoneOffset } from 'date-fns-tz';
+
+import { ianaTimeZones } from '@/settings/accounts/constants/ianaTimeZones';
+import { formatTimeZoneLabel } from '@/settings/accounts/utils/formatTimeZoneLabel';
+import { SelectOption } from '@/ui/input/components/Select';
+
+export const availableTimeZoneOptionsByLabel = ianaTimeZones.reduce<
+ Record>
+>((result, ianaTimeZone) => {
+ const timeZoneLabel = formatTimeZoneLabel(ianaTimeZone);
+
+ // Remove the '(GMT±00:00) ' prefix from the label.
+ const timeZoneName = timeZoneLabel.slice(11);
+
+ // Skip time zones with GMT, UTC, or UCT in their name,
+ // and duplicates.
+ if (
+ timeZoneName.includes('GMT') ||
+ timeZoneName.includes('UTC') ||
+ timeZoneName.includes('UCT') ||
+ timeZoneLabel in result
+ ) {
+ return result;
+ }
+
+ return {
+ ...result,
+ [timeZoneLabel]: { label: timeZoneLabel, value: ianaTimeZone },
+ };
+}, {});
+
+export const availableTimeZoneOptions = Object.values(
+ availableTimeZoneOptionsByLabel,
+).sort((optionA, optionB) => {
+ const difference =
+ getTimezoneOffset(optionA.value) - getTimezoneOffset(optionB.value);
+
+ return difference === 0
+ ? // Sort alphabetically if the time zone offsets are the same.
+ optionA.label.localeCompare(optionB.label)
+ : // Sort by time zone offset if different.
+ difference;
+});
diff --git a/packages/twenty-front/src/modules/settings/accounts/utils/detectTimeZone.ts b/packages/twenty-front/src/modules/settings/accounts/utils/detectTimeZone.ts
new file mode 100644
index 0000000000..95bb9ccc62
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/utils/detectTimeZone.ts
@@ -0,0 +1,6 @@
+/**
+ * Detects the user's time zone.
+ * @returns a IANA time zone
+ */
+export const detectTimeZone = () =>
+ Intl.DateTimeFormat().resolvedOptions().timeZone;
diff --git a/packages/twenty-front/src/modules/settings/accounts/utils/findAvailableTimeZoneOption.ts b/packages/twenty-front/src/modules/settings/accounts/utils/findAvailableTimeZoneOption.ts
new file mode 100644
index 0000000000..8f5795cfe5
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/utils/findAvailableTimeZoneOption.ts
@@ -0,0 +1,10 @@
+import { availableTimeZoneOptionsByLabel } from '@/settings/accounts/constants/timeZoneSelectOptions';
+import { formatTimeZoneLabel } from '@/settings/accounts/utils/formatTimeZoneLabel';
+
+/**
+ * Finds the matching available IANA time zone select option from a given IANA time zone.
+ * @param value the IANA time zone to match
+ * @returns the matching available IANA time zone select option or undefined
+ */
+export const findAvailableTimeZoneOption = (value: string) =>
+ availableTimeZoneOptionsByLabel[formatTimeZoneLabel(value)];
diff --git a/packages/twenty-front/src/modules/settings/accounts/utils/formatTimeZoneLabel.ts b/packages/twenty-front/src/modules/settings/accounts/utils/formatTimeZoneLabel.ts
new file mode 100644
index 0000000000..e5f1ac97e0
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/accounts/utils/formatTimeZoneLabel.ts
@@ -0,0 +1,29 @@
+import defaultLocale from 'date-fns/locale/en-US';
+import { formatInTimeZone } from 'date-fns-tz';
+
+/**
+ * Formats a IANA time zone to a select option label.
+ * @param ianaTimeZone IANA time zone
+ * @returns Formatted label
+ * @example 'Europe/Paris' => '(GMT+01:00) Central European Time - Paris'
+ */
+export const formatTimeZoneLabel = (ianaTimeZone: string) => {
+ const timeZoneWithGmtOffset = formatInTimeZone(
+ Date.now(),
+ ianaTimeZone,
+ `(OOOO) zzzz`,
+ { locale: defaultLocale },
+ );
+ const ianaTimeZoneParts = ianaTimeZone.split('/');
+ const location =
+ ianaTimeZoneParts.length > 1
+ ? ianaTimeZoneParts.slice(-1)[0].replaceAll('_', ' ')
+ : undefined;
+
+ const timeZoneLabel =
+ !location || timeZoneWithGmtOffset.includes(location)
+ ? timeZoneWithGmtOffset
+ : [timeZoneWithGmtOffset, location].join(' - ');
+
+ return timeZoneLabel;
+};
diff --git a/packages/twenty-front/src/modules/ui/input/components/Select.tsx b/packages/twenty-front/src/modules/ui/input/components/Select.tsx
index 89ad89eb43..531f00f866 100644
--- a/packages/twenty-front/src/modules/ui/input/components/Select.tsx
+++ b/packages/twenty-front/src/modules/ui/input/components/Select.tsx
@@ -1,3 +1,4 @@
+import { useMemo, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@@ -5,20 +6,30 @@ import { IconChevronDown } from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
+import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
+import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { SelectHotkeyScope } from '../types/SelectHotkeyScope';
+export type SelectOption = {
+ value: Value;
+ label: string;
+ Icon?: IconComponent;
+};
+
export type SelectProps = {
className?: string;
disabled?: boolean;
dropdownId: string;
+ dropdownWidth?: `${string}px` | 'auto' | number;
fullWidth?: boolean;
label?: string;
onChange?: (value: Value) => void;
- options: { value: Value; label: string; Icon?: IconComponent }[];
+ options: SelectOption[];
value?: Value;
+ withSearchInput?: boolean;
};
const StyledControlContainer = styled.div<{
@@ -63,15 +74,28 @@ export const Select = ({
className,
disabled,
dropdownId,
+ dropdownWidth = 176,
fullWidth,
label,
onChange,
options,
value,
+ withSearchInput,
}: SelectProps) => {
const theme = useTheme();
+ const [searchInputValue, setSearchInputValue] = useState('');
+
const selectedOption =
options.find(({ value: key }) => key === value) || options[0];
+ const filteredOptions = useMemo(
+ () =>
+ searchInputValue
+ ? options.filter(({ label }) =>
+ label.toLowerCase().includes(searchInputValue.toLowerCase()),
+ )
+ : options,
+ [options, searchInputValue],
+ );
const { closeDropdown } = useDropdown(dropdownId);
@@ -101,23 +125,37 @@ export const Select = ({
{!!label && {label}}
- {options.map((option) => (
-