{
+ const classes = classNames('tooltip', `tooltip--${position}`, {});
+
+ return (
+
+ );
+};
+
+Tooltip.displayName = 'Tooltip';
+
+Tooltip.propTypes = {
+ text: PropTypes.string,
+ position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),
+ children: PropTypes.node,
+};
+
+export default Tooltip;
diff --git a/app/components/ui/tooltip.scss b/app/components/ui/tooltip.scss
new file mode 100644
index 0000000..6f0741f
--- /dev/null
+++ b/app/components/ui/tooltip.scss
@@ -0,0 +1,99 @@
+@import '../../styles/global.scss';
+
+.tooltip-container {
+ position: relative;
+
+ &:hover .tooltip {
+ cursor: pointer;
+ display: block;
+ }
+}
+
+.tooltip {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: var(--accent-600);
+ color: var(--font-color);
+ padding: pxToRem(8);
+ font-size: var(--font-md);
+ font-weight: var(--weight-medium);
+ border-radius: var(--radius-md);
+ transition: all 0.15s ease-in-out;
+ z-index: 99;
+ width: max-content;
+ text-align: center;
+ display: none;
+
+ &.tooltip--top {
+ transform: translate(-50%, -200%);
+ }
+
+ &.tooltip--bottom {
+ transform: translate(-50%, 100%);
+ }
+
+ &.tooltip--left {
+ transform: translate(-150%, -50%);
+ }
+
+ &.tooltip--right {
+ transform: translate(50%, -50%);
+ }
+
+ &.tooltip--top,
+ &.tooltip--bottom,
+ &.tooltip--left,
+ &.tooltip--right {
+ max-width: 200px;
+ }
+
+ &:hover {
+ display: block;
+ }
+}
+
+.tooltip--primary {
+ background-color: var(--primary-700);
+ color: var(--font-color);
+}
+
+.tooltip--blue {
+ background-color: var(--blue-700);
+ color: var(--font-color);
+}
+
+.tooltip--cyan {
+ background-color: var(--cyan-700);
+ color: var(--font-color);
+}
+.tooltip--green {
+ background-color: var(--green-700);
+ color: var(--font-color);
+}
+
+.tooltip--pink {
+ background-color: var(--pink-700);
+ color: var(--font-color);
+}
+
+.tooltip--purple {
+ background-color: var(--purple-700);
+ color: var(--font-color);
+}
+
+.tooltip--red {
+ background-color: var(--red-700);
+ color: var(--font-color);
+}
+
+.tooltip--yellow {
+ background-color: var(--yellow-700);
+ color: var(--font-color);
+}
+
+.tooltip--gray {
+ background-color: var(--gray-700);
+ color: var(--font-color);
+}
diff --git a/app/constant/timeformats.json b/app/constant/timeformats.json
deleted file mode 100644
index 63ba729..0000000
--- a/app/constant/timeformats.json
+++ /dev/null
@@ -1,1698 +0,0 @@
-[
- {
- "name": "Pacific/Midway (GMT-11:00)",
- "value": "Pacific/Midway"
- },
- {
- "name": "Pacific/Niue (GMT-11:00)",
- "value": "Pacific/Niue"
- },
- {
- "name": "Pacific/Pago_Pago (GMT-11:00)",
- "value": "Pacific/Pago_Pago"
- },
- {
- "name": "America/Adak (GMT-10:00)",
- "value": "America/Adak"
- },
- {
- "name": "Pacific/Honolulu (GMT-10:00)",
- "value": "Pacific/Honolulu"
- },
- {
- "name": "Pacific/Rarotonga (GMT-10:00)",
- "value": "Pacific/Rarotonga"
- },
- {
- "name": "Pacific/Tahiti (GMT-10:00)",
- "value": "Pacific/Tahiti"
- },
- {
- "name": "Pacific/Marquesas (GMT-09:30)",
- "value": "Pacific/Marquesas"
- },
- {
- "name": "America/Anchorage (GMT-09:00)",
- "value": "America/Anchorage"
- },
- {
- "name": "America/Juneau (GMT-09:00)",
- "value": "America/Juneau"
- },
- {
- "name": "America/Metlakatla (GMT-09:00)",
- "value": "America/Metlakatla"
- },
- {
- "name": "America/Nome (GMT-09:00)",
- "value": "America/Nome"
- },
- {
- "name": "America/Sitka (GMT-09:00)",
- "value": "America/Sitka"
- },
- {
- "name": "America/Yakutat (GMT-09:00)",
- "value": "America/Yakutat"
- },
- {
- "name": "Pacific/Gambier (GMT-09:00)",
- "value": "Pacific/Gambier"
- },
- {
- "name": "America/Los_Angeles (GMT-08:00)",
- "value": "America/Los_Angeles"
- },
- {
- "name": "America/Tijuana (GMT-08:00)",
- "value": "America/Tijuana"
- },
- {
- "name": "America/Vancouver (GMT-08:00)",
- "value": "America/Vancouver"
- },
- {
- "name": "Pacific/Pitcairn (GMT-08:00)",
- "value": "Pacific/Pitcairn"
- },
- {
- "name": "America/Boise (GMT-07:00)",
- "value": "America/Boise"
- },
- {
- "name": "America/Cambridge_Bay (GMT-07:00)",
- "value": "America/Cambridge_Bay"
- },
- {
- "name": "America/Chihuahua (GMT-07:00)",
- "value": "America/Chihuahua"
- },
- {
- "name": "America/Creston (GMT-07:00)",
- "value": "America/Creston"
- },
- {
- "name": "America/Dawson (GMT-07:00)",
- "value": "America/Dawson"
- },
- {
- "name": "America/Dawson_Creek (GMT-07:00)",
- "value": "America/Dawson_Creek"
- },
- {
- "name": "America/Denver (GMT-07:00)",
- "value": "America/Denver"
- },
- {
- "name": "America/Edmonton (GMT-07:00)",
- "value": "America/Edmonton"
- },
- {
- "name": "America/Fort_Nelson (GMT-07:00)",
- "value": "America/Fort_Nelson"
- },
- {
- "name": "America/Hermosillo (GMT-07:00)",
- "value": "America/Hermosillo"
- },
- {
- "name": "America/Inuvik (GMT-07:00)",
- "value": "America/Inuvik"
- },
- {
- "name": "America/Mazatlan (GMT-07:00)",
- "value": "America/Mazatlan"
- },
- {
- "name": "America/Ojinaga (GMT-07:00)",
- "value": "America/Ojinaga"
- },
- {
- "name": "America/Phoenix (GMT-07:00)",
- "value": "America/Phoenix"
- },
- {
- "name": "America/Whitehorse (GMT-07:00)",
- "value": "America/Whitehorse"
- },
- {
- "name": "America/Yellowknife (GMT-07:00)",
- "value": "America/Yellowknife"
- },
- {
- "name": "America/Bahia_Banderas (GMT-06:00)",
- "value": "America/Bahia_Banderas"
- },
- {
- "name": "America/Belize (GMT-06:00)",
- "value": "America/Belize"
- },
- {
- "name": "America/Chicago (GMT-06:00)",
- "value": "America/Chicago"
- },
- {
- "name": "America/Costa_Rica (GMT-06:00)",
- "value": "America/Costa_Rica"
- },
- {
- "name": "America/El_Salvador (GMT-06:00)",
- "value": "America/El_Salvador"
- },
- {
- "name": "America/Guatemala (GMT-06:00)",
- "value": "America/Guatemala"
- },
- {
- "name": "America/Indiana/Knox (GMT-06:00)",
- "value": "America/Indiana/Knox"
- },
- {
- "name": "America/Indiana/Tell_City (GMT-06:00)",
- "value": "America/Indiana/Tell_City"
- },
- {
- "name": "America/Managua (GMT-06:00)",
- "value": "America/Managua"
- },
- {
- "name": "America/Matamoros (GMT-06:00)",
- "value": "America/Matamoros"
- },
- {
- "name": "America/Menominee (GMT-06:00)",
- "value": "America/Menominee"
- },
- {
- "name": "America/Merida (GMT-06:00)",
- "value": "America/Merida"
- },
- {
- "name": "America/Mexico_City (GMT-06:00)",
- "value": "America/Mexico_City"
- },
- {
- "name": "America/Monterrey (GMT-06:00)",
- "value": "America/Monterrey"
- },
- {
- "name": "America/North_Dakota/Beulah (GMT-06:00)",
- "value": "America/North_Dakota/Beulah"
- },
- {
- "name": "America/North_Dakota/Center (GMT-06:00)",
- "value": "America/North_Dakota/Center"
- },
- {
- "name": "America/North_Dakota/New_Salem (GMT-06:00)",
- "value": "America/North_Dakota/New_Salem"
- },
- {
- "name": "America/Rainy_River (GMT-06:00)",
- "value": "America/Rainy_River"
- },
- {
- "name": "America/Rankin_Inlet (GMT-06:00)",
- "value": "America/Rankin_Inlet"
- },
- {
- "name": "America/Regina (GMT-06:00)",
- "value": "America/Regina"
- },
- {
- "name": "America/Resolute (GMT-06:00)",
- "value": "America/Resolute"
- },
- {
- "name": "America/Swift_Current (GMT-06:00)",
- "value": "America/Swift_Current"
- },
- {
- "name": "America/Tegucigalpa (GMT-06:00)",
- "value": "America/Tegucigalpa"
- },
- {
- "name": "America/Winnipeg (GMT-06:00)",
- "value": "America/Winnipeg"
- },
- {
- "name": "Pacific/Easter (GMT-06:00)",
- "value": "Pacific/Easter"
- },
- {
- "name": "Pacific/Galapagos (GMT-06:00)",
- "value": "Pacific/Galapagos"
- },
- {
- "name": "America/Atikokan (GMT-05:00)",
- "value": "America/Atikokan"
- },
- {
- "name": "America/Bogota (GMT-05:00)",
- "value": "America/Bogota"
- },
- {
- "name": "America/Cancun (GMT-05:00)",
- "value": "America/Cancun"
- },
- {
- "name": "America/Cayman (GMT-05:00)",
- "value": "America/Cayman"
- },
- {
- "name": "America/Detroit (GMT-05:00)",
- "value": "America/Detroit"
- },
- {
- "name": "America/Eirunepe (GMT-05:00)",
- "value": "America/Eirunepe"
- },
- {
- "name": "America/Grand_Turk (GMT-05:00)",
- "value": "America/Grand_Turk"
- },
- {
- "name": "America/Guayaquil (GMT-05:00)",
- "value": "America/Guayaquil"
- },
- {
- "name": "America/Havana (GMT-05:00)",
- "value": "America/Havana"
- },
- {
- "name": "America/Indiana/Indianapolis (GMT-05:00)",
- "value": "America/Indiana/Indianapolis"
- },
- {
- "name": "America/Indiana/Marengo (GMT-05:00)",
- "value": "America/Indiana/Marengo"
- },
- {
- "name": "America/Indiana/Petersburg (GMT-05:00)",
- "value": "America/Indiana/Petersburg"
- },
- {
- "name": "America/Indiana/Vevay (GMT-05:00)",
- "value": "America/Indiana/Vevay"
- },
- {
- "name": "America/Indiana/Vincennes (GMT-05:00)",
- "value": "America/Indiana/Vincennes"
- },
- {
- "name": "America/Indiana/Winamac (GMT-05:00)",
- "value": "America/Indiana/Winamac"
- },
- {
- "name": "America/Iqaluit (GMT-05:00)",
- "value": "America/Iqaluit"
- },
- {
- "name": "America/Jamaica (GMT-05:00)",
- "value": "America/Jamaica"
- },
- {
- "name": "America/Kentucky/Louisville (GMT-05:00)",
- "value": "America/Kentucky/Louisville"
- },
- {
- "name": "America/Kentucky/Monticello (GMT-05:00)",
- "value": "America/Kentucky/Monticello"
- },
- {
- "name": "America/Lima (GMT-05:00)",
- "value": "America/Lima"
- },
- {
- "name": "America/Nassau (GMT-05:00)",
- "value": "America/Nassau"
- },
- {
- "name": "America/New_York (GMT-05:00)",
- "value": "America/New_York"
- },
- {
- "name": "America/Nipigon (GMT-05:00)",
- "value": "America/Nipigon"
- },
- {
- "name": "America/Panama (GMT-05:00)",
- "value": "America/Panama"
- },
- {
- "name": "America/Pangnirtung (GMT-05:00)",
- "value": "America/Pangnirtung"
- },
- {
- "name": "America/Port-au-Prince (GMT-05:00)",
- "value": "America/Port-au-Prince"
- },
- {
- "name": "America/Rio_Branco (GMT-05:00)",
- "value": "America/Rio_Branco"
- },
- {
- "name": "America/Thunder_Bay (GMT-05:00)",
- "value": "America/Thunder_Bay"
- },
- {
- "name": "America/Toronto (GMT-05:00)",
- "value": "America/Toronto"
- },
- {
- "name": "America/Anguilla (GMT-04:00)",
- "value": "America/Anguilla"
- },
- {
- "name": "America/Antigua (GMT-04:00)",
- "value": "America/Antigua"
- },
- {
- "name": "America/Aruba (GMT-04:00)",
- "value": "America/Aruba"
- },
- {
- "name": "America/Asuncion (GMT-04:00)",
- "value": "America/Asuncion"
- },
- {
- "name": "America/Barbados (GMT-04:00)",
- "value": "America/Barbados"
- },
- {
- "name": "America/Blanc-Sablon (GMT-04:00)",
- "value": "America/Blanc-Sablon"
- },
- {
- "name": "America/Boa_Vista (GMT-04:00)",
- "value": "America/Boa_Vista"
- },
- {
- "name": "America/Campo_Grande (GMT-04:00)",
- "value": "America/Campo_Grande"
- },
- {
- "name": "America/Caracas (GMT-04:00)",
- "value": "America/Caracas"
- },
- {
- "name": "America/Cuiaba (GMT-04:00)",
- "value": "America/Cuiaba"
- },
- {
- "name": "America/Curacao (GMT-04:00)",
- "value": "America/Curacao"
- },
- {
- "name": "America/Dominica (GMT-04:00)",
- "value": "America/Dominica"
- },
- {
- "name": "America/Glace_Bay (GMT-04:00)",
- "value": "America/Glace_Bay"
- },
- {
- "name": "America/Goose_Bay (GMT-04:00)",
- "value": "America/Goose_Bay"
- },
- {
- "name": "America/Grenada (GMT-04:00)",
- "value": "America/Grenada"
- },
- {
- "name": "America/Guadeloupe (GMT-04:00)",
- "value": "America/Guadeloupe"
- },
- {
- "name": "America/Guyana (GMT-04:00)",
- "value": "America/Guyana"
- },
- {
- "name": "America/Halifax (GMT-04:00)",
- "value": "America/Halifax"
- },
- {
- "name": "America/Kralendijk (GMT-04:00)",
- "value": "America/Kralendijk"
- },
- {
- "name": "America/La_Paz (GMT-04:00)",
- "value": "America/La_Paz"
- },
- {
- "name": "America/Lower_Princes (GMT-04:00)",
- "value": "America/Lower_Princes"
- },
- {
- "name": "America/Manaus (GMT-04:00)",
- "value": "America/Manaus"
- },
- {
- "name": "America/Marigot (GMT-04:00)",
- "value": "America/Marigot"
- },
- {
- "name": "America/Martinique (GMT-04:00)",
- "value": "America/Martinique"
- },
- {
- "name": "America/Moncton (GMT-04:00)",
- "value": "America/Moncton"
- },
- {
- "name": "America/Montserrat (GMT-04:00)",
- "value": "America/Montserrat"
- },
- {
- "name": "America/Porto_Velho (GMT-04:00)",
- "value": "America/Porto_Velho"
- },
- {
- "name": "America/Port_of_Spain (GMT-04:00)",
- "value": "America/Port_of_Spain"
- },
- {
- "name": "America/Puerto_Rico (GMT-04:00)",
- "value": "America/Puerto_Rico"
- },
- {
- "name": "America/Santiago (GMT-04:00)",
- "value": "America/Santiago"
- },
- {
- "name": "America/Santo_Domingo (GMT-04:00)",
- "value": "America/Santo_Domingo"
- },
- {
- "name": "America/St_Barthelemy (GMT-04:00)",
- "value": "America/St_Barthelemy"
- },
- {
- "name": "America/St_Kitts (GMT-04:00)",
- "value": "America/St_Kitts"
- },
- {
- "name": "America/St_Lucia (GMT-04:00)",
- "value": "America/St_Lucia"
- },
- {
- "name": "America/St_Thomas (GMT-04:00)",
- "value": "America/St_Thomas"
- },
- {
- "name": "America/St_Vincent (GMT-04:00)",
- "value": "America/St_Vincent"
- },
- {
- "name": "America/Thule (GMT-04:00)",
- "value": "America/Thule"
- },
- {
- "name": "America/Tortola (GMT-04:00)",
- "value": "America/Tortola"
- },
- {
- "name": "Atlantic/Bermuda (GMT-04:00)",
- "value": "Atlantic/Bermuda"
- },
- {
- "name": "America/St_Johns (GMT-03:30)",
- "value": "America/St_Johns"
- },
- {
- "name": "America/Araguaina (GMT-03:00)",
- "value": "America/Araguaina"
- },
- {
- "name": "America/Argentina/Buenos_Aires (GMT-03:00)",
- "value": "America/Argentina/Buenos_Aires"
- },
- {
- "name": "America/Argentina/Catamarca (GMT-03:00)",
- "value": "America/Argentina/Catamarca"
- },
- {
- "name": "America/Argentina/Cordoba (GMT-03:00)",
- "value": "America/Argentina/Cordoba"
- },
- {
- "name": "America/Argentina/Jujuy (GMT-03:00)",
- "value": "America/Argentina/Jujuy"
- },
- {
- "name": "America/Argentina/La_Rioja (GMT-03:00)",
- "value": "America/Argentina/La_Rioja"
- },
- {
- "name": "America/Argentina/Mendoza (GMT-03:00)",
- "value": "America/Argentina/Mendoza"
- },
- {
- "name": "America/Argentina/Rio_Gallegos (GMT-03:00)",
- "value": "America/Argentina/Rio_Gallegos"
- },
- {
- "name": "America/Argentina/Salta (GMT-03:00)",
- "value": "America/Argentina/Salta"
- },
- {
- "name": "America/Argentina/San_Juan (GMT-03:00)",
- "value": "America/Argentina/San_Juan"
- },
- {
- "name": "America/Argentina/San_Luis (GMT-03:00)",
- "value": "America/Argentina/San_Luis"
- },
- {
- "name": "America/Argentina/Tucuman (GMT-03:00)",
- "value": "America/Argentina/Tucuman"
- },
- {
- "name": "America/Argentina/Ushuaia (GMT-03:00)",
- "value": "America/Argentina/Ushuaia"
- },
- {
- "name": "America/Bahia (GMT-03:00)",
- "value": "America/Bahia"
- },
- {
- "name": "America/Belem (GMT-03:00)",
- "value": "America/Belem"
- },
- {
- "name": "America/Cayenne (GMT-03:00)",
- "value": "America/Cayenne"
- },
- {
- "name": "America/Fortaleza (GMT-03:00)",
- "value": "America/Fortaleza"
- },
- {
- "name": "America/Godthab (GMT-03:00)",
- "value": "America/Godthab"
- },
- {
- "name": "America/Maceio (GMT-03:00)",
- "value": "America/Maceio"
- },
- {
- "name": "America/Miquelon (GMT-03:00)",
- "value": "America/Miquelon"
- },
- {
- "name": "America/Montevideo (GMT-03:00)",
- "value": "America/Montevideo"
- },
- {
- "name": "America/Paramaribo (GMT-03:00)",
- "value": "America/Paramaribo"
- },
- {
- "name": "America/Punta_Arenas (GMT-03:00)",
- "value": "America/Punta_Arenas"
- },
- {
- "name": "America/Recife (GMT-03:00)",
- "value": "America/Recife"
- },
- {
- "name": "America/Santarem (GMT-03:00)",
- "value": "America/Santarem"
- },
- {
- "name": "America/Sao_Paulo (GMT-03:00)",
- "value": "America/Sao_Paulo"
- },
- {
- "name": "Antarctica/Palmer (GMT-03:00)",
- "value": "Antarctica/Palmer"
- },
- {
- "name": "Antarctica/Rothera (GMT-03:00)",
- "value": "Antarctica/Rothera"
- },
- {
- "name": "Atlantic/Stanley (GMT-03:00)",
- "value": "Atlantic/Stanley"
- },
- {
- "name": "America/Noronha (GMT-02:00)",
- "value": "America/Noronha"
- },
- {
- "name": "Atlantic/South_Georgia (GMT-02:00)",
- "value": "Atlantic/South_Georgia"
- },
- {
- "name": "America/Scoresbysund (GMT-01:00)",
- "value": "America/Scoresbysund"
- },
- {
- "name": "Atlantic/Azores (GMT-01:00)",
- "value": "Atlantic/Azores"
- },
- {
- "name": "Atlantic/Cape_Verde (GMT-01:00)",
- "value": "Atlantic/Cape_Verde"
- },
- {
- "name": "Africa/Abidjan (GMT+00:00)",
- "value": "Africa/Abidjan"
- },
- {
- "name": "Africa/Accra (GMT+00:00)",
- "value": "Africa/Accra"
- },
- {
- "name": "Africa/Bamako (GMT+00:00)",
- "value": "Africa/Bamako"
- },
- {
- "name": "Africa/Banjul (GMT+00:00)",
- "value": "Africa/Banjul"
- },
- {
- "name": "Africa/Bissau (GMT+00:00)",
- "value": "Africa/Bissau"
- },
- {
- "name": "Africa/Casablanca (GMT+00:00)",
- "value": "Africa/Casablanca"
- },
- {
- "name": "Africa/Conakry (GMT+00:00)",
- "value": "Africa/Conakry"
- },
- {
- "name": "Africa/Dakar (GMT+00:00)",
- "value": "Africa/Dakar"
- },
- {
- "name": "Africa/El_Aaiun (GMT+00:00)",
- "value": "Africa/El_Aaiun"
- },
- {
- "name": "Africa/Freetown (GMT+00:00)",
- "value": "Africa/Freetown"
- },
- {
- "name": "Africa/Lome (GMT+00:00)",
- "value": "Africa/Lome"
- },
- {
- "name": "Africa/Monrovia (GMT+00:00)",
- "value": "Africa/Monrovia"
- },
- {
- "name": "Africa/Nouakchott (GMT+00:00)",
- "value": "Africa/Nouakchott"
- },
- {
- "name": "Africa/Ouagadougou (GMT+00:00)",
- "value": "Africa/Ouagadougou"
- },
- {
- "name": "Africa/Sao_Tome (GMT+00:00)",
- "value": "Africa/Sao_Tome"
- },
- {
- "name": "America/Danmarkshavn (GMT+00:00)",
- "value": "America/Danmarkshavn"
- },
- {
- "name": "Antarctica/Troll (GMT+00:00)",
- "value": "Antarctica/Troll"
- },
- {
- "name": "Atlantic/Canary (GMT+00:00)",
- "value": "Atlantic/Canary"
- },
- {
- "name": "Atlantic/Faroe (GMT+00:00)",
- "value": "Atlantic/Faroe"
- },
- {
- "name": "Atlantic/Madeira (GMT+00:00)",
- "value": "Atlantic/Madeira"
- },
- {
- "name": "Atlantic/Reykjavik (GMT+00:00)",
- "value": "Atlantic/Reykjavik"
- },
- {
- "name": "Atlantic/St_Helena (GMT+00:00)",
- "value": "Atlantic/St_Helena"
- },
- {
- "name": "Europe/Dublin (GMT+00:00)",
- "value": "Europe/Dublin"
- },
- {
- "name": "Europe/Guernsey (GMT+00:00)",
- "value": "Europe/Guernsey"
- },
- {
- "name": "Europe/Isle_of_Man (GMT+00:00)",
- "value": "Europe/Isle_of_Man"
- },
- {
- "name": "Europe/Jersey (GMT+00:00)",
- "value": "Europe/Jersey"
- },
- {
- "name": "Europe/Lisbon (GMT+00:00)",
- "value": "Europe/Lisbon"
- },
- {
- "name": "Europe/London (GMT+00:00)",
- "value": "Europe/London"
- },
- {
- "name": "Africa/Algiers (GMT+01:00)",
- "value": "Africa/Algiers"
- },
- {
- "name": "Africa/Bangui (GMT+01:00)",
- "value": "Africa/Bangui"
- },
- {
- "name": "Africa/Brazzaville (GMT+01:00)",
- "value": "Africa/Brazzaville"
- },
- {
- "name": "Africa/Ceuta (GMT+01:00)",
- "value": "Africa/Ceuta"
- },
- {
- "name": "Africa/Douala (GMT+01:00)",
- "value": "Africa/Douala"
- },
- {
- "name": "Africa/Kinshasa (GMT+01:00)",
- "value": "Africa/Kinshasa"
- },
- {
- "name": "Africa/Lagos (GMT+01:00)",
- "value": "Africa/Lagos"
- },
- {
- "name": "Africa/Libreville (GMT+01:00)",
- "value": "Africa/Libreville"
- },
- {
- "name": "Africa/Luanda (GMT+01:00)",
- "value": "Africa/Luanda"
- },
- {
- "name": "Africa/Malabo (GMT+01:00)",
- "value": "Africa/Malabo"
- },
- {
- "name": "Africa/Ndjamena (GMT+01:00)",
- "value": "Africa/Ndjamena"
- },
- {
- "name": "Africa/Niamey (GMT+01:00)",
- "value": "Africa/Niamey"
- },
- {
- "name": "Africa/Porto-Novo (GMT+01:00)",
- "value": "Africa/Porto-Novo"
- },
- {
- "name": "Africa/Tunis (GMT+01:00)",
- "value": "Africa/Tunis"
- },
- {
- "name": "Africa/Windhoek (GMT+01:00)",
- "value": "Africa/Windhoek"
- },
- {
- "name": "Arctic/Longyearbyen (GMT+01:00)",
- "value": "Arctic/Longyearbyen"
- },
- {
- "name": "Europe/Amsterdam (GMT+01:00)",
- "value": "Europe/Amsterdam"
- },
- {
- "name": "Europe/Andorra (GMT+01:00)",
- "value": "Europe/Andorra"
- },
- {
- "name": "Europe/Belgrade (GMT+01:00)",
- "value": "Europe/Belgrade"
- },
- {
- "name": "Europe/Berlin (GMT+01:00)",
- "value": "Europe/Berlin"
- },
- {
- "name": "Europe/Bratislava (GMT+01:00)",
- "value": "Europe/Bratislava"
- },
- {
- "name": "Europe/Brussels (GMT+01:00)",
- "value": "Europe/Brussels"
- },
- {
- "name": "Europe/Budapest (GMT+01:00)",
- "value": "Europe/Budapest"
- },
- {
- "name": "Europe/Copenhagen (GMT+01:00)",
- "value": "Europe/Copenhagen"
- },
- {
- "name": "Europe/Gibraltar (GMT+01:00)",
- "value": "Europe/Gibraltar"
- },
- {
- "name": "Europe/Ljubljana (GMT+01:00)",
- "value": "Europe/Ljubljana"
- },
- {
- "name": "Europe/Luxembourg (GMT+01:00)",
- "value": "Europe/Luxembourg"
- },
- {
- "name": "Europe/Madrid (GMT+01:00)",
- "value": "Europe/Madrid"
- },
- {
- "name": "Europe/Malta (GMT+01:00)",
- "value": "Europe/Malta"
- },
- {
- "name": "Europe/Monaco (GMT+01:00)",
- "value": "Europe/Monaco"
- },
- {
- "name": "Europe/Oslo (GMT+01:00)",
- "value": "Europe/Oslo"
- },
- {
- "name": "Europe/Paris (GMT+01:00)",
- "value": "Europe/Paris"
- },
- {
- "name": "Europe/Podgorica (GMT+01:00)",
- "value": "Europe/Podgorica"
- },
- {
- "name": "Europe/Prague (GMT+01:00)",
- "value": "Europe/Prague"
- },
- {
- "name": "Europe/Rome (GMT+01:00)",
- "value": "Europe/Rome"
- },
- {
- "name": "Europe/San_Marino (GMT+01:00)",
- "value": "Europe/San_Marino"
- },
- {
- "name": "Europe/Sarajevo (GMT+01:00)",
- "value": "Europe/Sarajevo"
- },
- {
- "name": "Europe/Skopje (GMT+01:00)",
- "value": "Europe/Skopje"
- },
- {
- "name": "Europe/Stockholm (GMT+01:00)",
- "value": "Europe/Stockholm"
- },
- {
- "name": "Europe/Tirane (GMT+01:00)",
- "value": "Europe/Tirane"
- },
- {
- "name": "Europe/Vaduz (GMT+01:00)",
- "value": "Europe/Vaduz"
- },
- {
- "name": "Europe/Vatican (GMT+01:00)",
- "value": "Europe/Vatican"
- },
- {
- "name": "Europe/Vienna (GMT+01:00)",
- "value": "Europe/Vienna"
- },
- {
- "name": "Europe/Warsaw (GMT+01:00)",
- "value": "Europe/Warsaw"
- },
- {
- "name": "Europe/Zagreb (GMT+01:00)",
- "value": "Europe/Zagreb"
- },
- {
- "name": "Europe/Zurich (GMT+01:00)",
- "value": "Europe/Zurich"
- },
- {
- "name": "Africa/Blantyre (GMT+02:00)",
- "value": "Africa/Blantyre"
- },
- {
- "name": "Africa/Bujumbura (GMT+02:00)",
- "value": "Africa/Bujumbura"
- },
- {
- "name": "Africa/Cairo (GMT+02:00)",
- "value": "Africa/Cairo"
- },
- {
- "name": "Africa/Gaborone (GMT+02:00)",
- "value": "Africa/Gaborone"
- },
- {
- "name": "Africa/Harare (GMT+02:00)",
- "value": "Africa/Harare"
- },
- {
- "name": "Africa/Johannesburg (GMT+02:00)",
- "value": "Africa/Johannesburg"
- },
- {
- "name": "Africa/Juba (GMT+02:00)",
- "value": "Africa/Juba"
- },
- {
- "name": "Africa/Khartoum (GMT+02:00)",
- "value": "Africa/Khartoum"
- },
- {
- "name": "Africa/Kigali (GMT+02:00)",
- "value": "Africa/Kigali"
- },
- {
- "name": "Africa/Lubumbashi (GMT+02:00)",
- "value": "Africa/Lubumbashi"
- },
- {
- "name": "Africa/Lusaka (GMT+02:00)",
- "value": "Africa/Lusaka"
- },
- {
- "name": "Africa/Maputo (GMT+02:00)",
- "value": "Africa/Maputo"
- },
- {
- "name": "Africa/Maseru (GMT+02:00)",
- "value": "Africa/Maseru"
- },
- {
- "name": "Africa/Mbabane (GMT+02:00)",
- "value": "Africa/Mbabane"
- },
- {
- "name": "Africa/Tripoli (GMT+02:00)",
- "value": "Africa/Tripoli"
- },
- {
- "name": "Asia/Amman (GMT+02:00)",
- "value": "Asia/Amman"
- },
- {
- "name": "Asia/Beirut (GMT+02:00)",
- "value": "Asia/Beirut"
- },
- {
- "name": "Asia/Damascus (GMT+02:00)",
- "value": "Asia/Damascus"
- },
- {
- "name": "Asia/Famagusta (GMT+02:00)",
- "value": "Asia/Famagusta"
- },
- {
- "name": "Asia/Gaza (GMT+02:00)",
- "value": "Asia/Gaza"
- },
- {
- "name": "Asia/Hebron (GMT+02:00)",
- "value": "Asia/Hebron"
- },
- {
- "name": "Asia/Jerusalem (GMT+02:00)",
- "value": "Asia/Jerusalem"
- },
- {
- "name": "Asia/Nicosia (GMT+02:00)",
- "value": "Asia/Nicosia"
- },
- {
- "name": "Europe/Athens (GMT+02:00)",
- "value": "Europe/Athens"
- },
- {
- "name": "Europe/Bucharest (GMT+02:00)",
- "value": "Europe/Bucharest"
- },
- {
- "name": "Europe/Chisinau (GMT+02:00)",
- "value": "Europe/Chisinau"
- },
- {
- "name": "Europe/Helsinki (GMT+02:00)",
- "value": "Europe/Helsinki"
- },
- {
- "name": "Europe/Kaliningrad (GMT+02:00)",
- "value": "Europe/Kaliningrad"
- },
- {
- "name": "Europe/Kyiv (GMT+02:00)",
- "value": "Europe/Kyiv"
- },
- {
- "name": "Europe/Mariehamn (GMT+02:00)",
- "value": "Europe/Mariehamn"
- },
- {
- "name": "Europe/Riga (GMT+02:00)",
- "value": "Europe/Riga"
- },
- {
- "name": "Europe/Sofia (GMT+02:00)",
- "value": "Europe/Sofia"
- },
- {
- "name": "Europe/Tallinn (GMT+02:00)",
- "value": "Europe/Tallinn"
- },
- {
- "name": "Europe/Uzhgorod (GMT+02:00)",
- "value": "Europe/Uzhgorod"
- },
- {
- "name": "Europe/Vilnius (GMT+02:00)",
- "value": "Europe/Vilnius"
- },
- {
- "name": "Europe/Zaporozhye (GMT+02:00)",
- "value": "Europe/Zaporozhye"
- },
- {
- "name": "Africa/Addis_Ababa (GMT+03:00)",
- "value": "Africa/Addis_Ababa"
- },
- {
- "name": "Africa/Asmara (GMT+03:00)",
- "value": "Africa/Asmara"
- },
- {
- "name": "Africa/Dar_es_Salaam (GMT+03:00)",
- "value": "Africa/Dar_es_Salaam"
- },
- {
- "name": "Africa/Djibouti (GMT+03:00)",
- "value": "Africa/Djibouti"
- },
- {
- "name": "Africa/Kampala (GMT+03:00)",
- "value": "Africa/Kampala"
- },
- {
- "name": "Africa/Mogadishu (GMT+03:00)",
- "value": "Africa/Mogadishu"
- },
- {
- "name": "Africa/Nairobi (GMT+03:00)",
- "value": "Africa/Nairobi"
- },
- {
- "name": "Antarctica/Syowa (GMT+03:00)",
- "value": "Antarctica/Syowa"
- },
- {
- "name": "Asia/Aden (GMT+03:00)",
- "value": "Asia/Aden"
- },
- {
- "name": "Asia/Baghdad (GMT+03:00)",
- "value": "Asia/Baghdad"
- },
- {
- "name": "Asia/Bahrain (GMT+03:00)",
- "value": "Asia/Bahrain"
- },
- {
- "name": "Asia/Kuwait (GMT+03:00)",
- "value": "Asia/Kuwait"
- },
- {
- "name": "Asia/Qatar (GMT+03:00)",
- "value": "Asia/Qatar"
- },
- {
- "name": "Asia/Riyadh (GMT+03:00)",
- "value": "Asia/Riyadh"
- },
- {
- "name": "Europe/Istanbul (GMT+03:00)",
- "value": "Europe/Istanbul"
- },
- {
- "name": "Europe/Kirov (GMT+03:00)",
- "value": "Europe/Kirov"
- },
- {
- "name": "Europe/Minsk (GMT+03:00)",
- "value": "Europe/Minsk"
- },
- {
- "name": "Europe/Moscow (GMT+03:00)",
- "value": "Europe/Moscow"
- },
- {
- "name": "Europe/Simferopol (GMT+03:00)",
- "value": "Europe/Simferopol"
- },
- {
- "name": "Europe/Volgograd (GMT+03:00)",
- "value": "Europe/Volgograd"
- },
- {
- "name": "Indian/Antananarivo (GMT+03:00)",
- "value": "Indian/Antananarivo"
- },
- {
- "name": "Indian/Comoro (GMT+03:00)",
- "value": "Indian/Comoro"
- },
- {
- "name": "Indian/Mayotte (GMT+03:00)",
- "value": "Indian/Mayotte"
- },
- {
- "name": "Asia/Tehran (GMT+03:30)",
- "value": "Asia/Tehran"
- },
- {
- "name": "Asia/Baku (GMT+04:00)",
- "value": "Asia/Baku"
- },
- {
- "name": "Asia/Dubai (GMT+04:00)",
- "value": "Asia/Dubai"
- },
- {
- "name": "Asia/Muscat (GMT+04:00)",
- "value": "Asia/Muscat"
- },
- {
- "name": "Asia/Tbilisi (GMT+04:00)",
- "value": "Asia/Tbilisi"
- },
- {
- "name": "Asia/Yerevan (GMT+04:00)",
- "value": "Asia/Yerevan"
- },
- {
- "name": "Europe/Astrakhan (GMT+04:00)",
- "value": "Europe/Astrakhan"
- },
- {
- "name": "Europe/Samara (GMT+04:00)",
- "value": "Europe/Samara"
- },
- {
- "name": "Europe/Saratov (GMT+04:00)",
- "value": "Europe/Saratov"
- },
- {
- "name": "Europe/Ulyanovsk (GMT+04:00)",
- "value": "Europe/Ulyanovsk"
- },
- {
- "name": "Indian/Mahe (GMT+04:00)",
- "value": "Indian/Mahe"
- },
- {
- "name": "Indian/Mauritius (GMT+04:00)",
- "value": "Indian/Mauritius"
- },
- {
- "name": "Indian/Reunion (GMT+04:00)",
- "value": "Indian/Reunion"
- },
- {
- "name": "Asia/Kabul (GMT+04:30)",
- "value": "Asia/Kabul"
- },
- {
- "name": "Antarctica/Mawson (GMT+05:00)",
- "value": "Antarctica/Mawson"
- },
- {
- "name": "Asia/Aqtau (GMT+05:00)",
- "value": "Asia/Aqtau"
- },
- {
- "name": "Asia/Aqtobe (GMT+05:00)",
- "value": "Asia/Aqtobe"
- },
- {
- "name": "Asia/Ashgabat (GMT+05:00)",
- "value": "Asia/Ashgabat"
- },
- {
- "name": "Asia/Atyrau (GMT+05:00)",
- "value": "Asia/Atyrau"
- },
- {
- "name": "Asia/Dushanbe (GMT+05:00)",
- "value": "Asia/Dushanbe"
- },
- {
- "name": "Asia/Karachi (GMT+05:00)",
- "value": "Asia/Karachi"
- },
- {
- "name": "Asia/Oral (GMT+05:00)",
- "value": "Asia/Oral"
- },
- {
- "name": "Asia/Qyzylorda (GMT+05:00)",
- "value": "Asia/Qyzylorda"
- },
- {
- "name": "Asia/Samarkand (GMT+05:00)",
- "value": "Asia/Samarkand"
- },
- {
- "name": "Asia/Tashkent (GMT+05:00)",
- "value": "Asia/Tashkent"
- },
- {
- "name": "Asia/Yekaterinburg (GMT+05:00)",
- "value": "Asia/Yekaterinburg"
- },
- {
- "name": "Indian/Kerguelen (GMT+05:00)",
- "value": "Indian/Kerguelen"
- },
- {
- "name": "Indian/Maldives (GMT+05:00)",
- "value": "Indian/Maldives"
- },
- {
- "name": "Asia/Colombo (GMT+05:30)",
- "value": "Asia/Colombo"
- },
- {
- "name": "Asia/Kolkata (GMT+05:30)",
- "value": "Asia/Kolkata"
- },
- {
- "name": "Asia/Kathmandu (GMT+05:45)",
- "value": "Asia/Kathmandu"
- },
- {
- "name": "Antarctica/Vostok (GMT+06:00)",
- "value": "Antarctica/Vostok"
- },
- {
- "name": "Asia/Almaty (GMT+06:00)",
- "value": "Asia/Almaty"
- },
- {
- "name": "Asia/Bishkek (GMT+06:00)",
- "value": "Asia/Bishkek"
- },
- {
- "name": "Asia/Dhaka (GMT+06:00)",
- "value": "Asia/Dhaka"
- },
- {
- "name": "Asia/Omsk (GMT+06:00)",
- "value": "Asia/Omsk"
- },
- {
- "name": "Asia/Qostanay (GMT+06:00)",
- "value": "Asia/Qostanay"
- },
- {
- "name": "Asia/Thimphu (GMT+06:00)",
- "value": "Asia/Thimphu"
- },
- {
- "name": "Asia/Urumqi (GMT+06:00)",
- "value": "Asia/Urumqi"
- },
- {
- "name": "Indian/Chagos (GMT+06:00)",
- "value": "Indian/Chagos"
- },
- {
- "name": "Asia/Yangon (GMT+06:30)",
- "value": "Asia/Yangon"
- },
- {
- "name": "Indian/Cocos (GMT+06:30)",
- "value": "Indian/Cocos"
- },
- {
- "name": "Antarctica/Davis (GMT+07:00)",
- "value": "Antarctica/Davis"
- },
- {
- "name": "Asia/Bangkok (GMT+07:00)",
- "value": "Asia/Bangkok"
- },
- {
- "name": "Asia/Barnaul (GMT+07:00)",
- "value": "Asia/Barnaul"
- },
- {
- "name": "Asia/Hovd (GMT+07:00)",
- "value": "Asia/Hovd"
- },
- {
- "name": "Asia/Ho_Chi_Minh (GMT+07:00)",
- "value": "Asia/Ho_Chi_Minh"
- },
- {
- "name": "Asia/Jakarta (GMT+07:00)",
- "value": "Asia/Jakarta"
- },
- {
- "name": "Asia/Krasnoyarsk (GMT+07:00)",
- "value": "Asia/Krasnoyarsk"
- },
- {
- "name": "Asia/Novokuznetsk (GMT+07:00)",
- "value": "Asia/Novokuznetsk"
- },
- {
- "name": "Asia/Novosibirsk (GMT+07:00)",
- "value": "Asia/Novosibirsk"
- },
- {
- "name": "Asia/Phnom_Penh (GMT+07:00)",
- "value": "Asia/Phnom_Penh"
- },
- {
- "name": "Asia/Pontianak (GMT+07:00)",
- "value": "Asia/Pontianak"
- },
- {
- "name": "Asia/Tomsk (GMT+07:00)",
- "value": "Asia/Tomsk"
- },
- {
- "name": "Asia/Vientiane (GMT+07:00)",
- "value": "Asia/Vientiane"
- },
- {
- "name": "Indian/Christmas (GMT+07:00)",
- "value": "Indian/Christmas"
- },
- {
- "name": "Asia/Brunei (GMT+08:00)",
- "value": "Asia/Brunei"
- },
- {
- "name": "Asia/Choibalsan (GMT+08:00)",
- "value": "Asia/Choibalsan"
- },
- {
- "name": "Asia/Hong_Kong (GMT+08:00)",
- "value": "Asia/Hong_Kong"
- },
- {
- "name": "Asia/Irkutsk (GMT+08:00)",
- "value": "Asia/Irkutsk"
- },
- {
- "name": "Asia/Kuala_Lumpur (GMT+08:00)",
- "value": "Asia/Kuala_Lumpur"
- },
- {
- "name": "Asia/Kuching (GMT+08:00)",
- "value": "Asia/Kuching"
- },
- {
- "name": "Asia/Macau (GMT+08:00)",
- "value": "Asia/Macau"
- },
- {
- "name": "Asia/Makassar (GMT+08:00)",
- "value": "Asia/Makassar"
- },
- {
- "name": "Asia/Manila (GMT+08:00)",
- "value": "Asia/Manila"
- },
- {
- "name": "Asia/Shanghai (GMT+08:00)",
- "value": "Asia/Shanghai"
- },
- {
- "name": "Asia/Singapore (GMT+08:00)",
- "value": "Asia/Singapore"
- },
- {
- "name": "Asia/Taipei (GMT+08:00)",
- "value": "Asia/Taipei"
- },
- {
- "name": "Asia/Ulaanbaatar (GMT+08:00)",
- "value": "Asia/Ulaanbaatar"
- },
- {
- "name": "Australia/Perth (GMT+08:00)",
- "value": "Australia/Perth"
- },
- {
- "name": "Australia/Eucla (GMT+08:45)",
- "value": "Australia/Eucla"
- },
- {
- "name": "Asia/Chita (GMT+09:00)",
- "value": "Asia/Chita"
- },
- {
- "name": "Asia/Dili (GMT+09:00)",
- "value": "Asia/Dili"
- },
- {
- "name": "Asia/Jayapura (GMT+09:00)",
- "value": "Asia/Jayapura"
- },
- {
- "name": "Asia/Khandyga (GMT+09:00)",
- "value": "Asia/Khandyga"
- },
- {
- "name": "Asia/Pyongyang (GMT+09:00)",
- "value": "Asia/Pyongyang"
- },
- {
- "name": "Asia/Seoul (GMT+09:00)",
- "value": "Asia/Seoul"
- },
- {
- "name": "Asia/Tokyo (GMT+09:00)",
- "value": "Asia/Tokyo"
- },
- {
- "name": "Asia/Yakutsk (GMT+09:00)",
- "value": "Asia/Yakutsk"
- },
- {
- "name": "Pacific/Palau (GMT+09:00)",
- "value": "Pacific/Palau"
- },
- {
- "name": "Australia/Adelaide (GMT+09:30)",
- "value": "Australia/Adelaide"
- },
- {
- "name": "Australia/Broken_Hill (GMT+09:30)",
- "value": "Australia/Broken_Hill"
- },
- {
- "name": "Australia/Darwin (GMT+09:30)",
- "value": "Australia/Darwin"
- },
- {
- "name": "Antarctica/DumontDUrville (GMT+10:00)",
- "value": "Antarctica/DumontDUrville"
- },
- {
- "name": "Antarctica/Macquarie (GMT+10:00)",
- "value": "Antarctica/Macquarie"
- },
- {
- "name": "Asia/Ust-Nera (GMT+10:00)",
- "value": "Asia/Ust-Nera"
- },
- {
- "name": "Asia/Vladivostok (GMT+10:00)",
- "value": "Asia/Vladivostok"
- },
- {
- "name": "Australia/Brisbane (GMT+10:00)",
- "value": "Australia/Brisbane"
- },
- {
- "name": "Australia/Currie (GMT+10:00)",
- "value": "Australia/Currie"
- },
- {
- "name": "Australia/Hobart (GMT+10:00)",
- "value": "Australia/Hobart"
- },
- {
- "name": "Australia/Lindeman (GMT+10:00)",
- "value": "Australia/Lindeman"
- },
- {
- "name": "Australia/Melbourne (GMT+10:00)",
- "value": "Australia/Melbourne"
- },
- {
- "name": "Australia/Sydney (GMT+10:00)",
- "value": "Australia/Sydney"
- },
- {
- "name": "Pacific/Chuuk (GMT+10:00)",
- "value": "Pacific/Chuuk"
- },
- {
- "name": "Pacific/Guam (GMT+10:00)",
- "value": "Pacific/Guam"
- },
- {
- "name": "Pacific/Port_Moresby (GMT+10:00)",
- "value": "Pacific/Port_Moresby"
- },
- {
- "name": "Pacific/Saipan (GMT+10:00)",
- "value": "Pacific/Saipan"
- },
- {
- "name": "Australia/Lord_Howe (GMT+10:30)",
- "value": "Australia/Lord_Howe"
- },
- {
- "name": "Antarctica/Casey (GMT+11:00)",
- "value": "Antarctica/Casey"
- },
- {
- "name": "Asia/Magadan (GMT+11:00)",
- "value": "Asia/Magadan"
- },
- {
- "name": "Asia/Sakhalin (GMT+11:00)",
- "value": "Asia/Sakhalin"
- },
- {
- "name": "Asia/Srednekolymsk (GMT+11:00)",
- "value": "Asia/Srednekolymsk"
- },
- {
- "name": "Pacific/Bougainville (GMT+11:00)",
- "value": "Pacific/Bougainville"
- },
- {
- "name": "Pacific/Efate (GMT+11:00)",
- "value": "Pacific/Efate"
- },
- {
- "name": "Pacific/Guadalcanal (GMT+11:00)",
- "value": "Pacific/Guadalcanal"
- },
- {
- "name": "Pacific/Kosrae (GMT+11:00)",
- "value": "Pacific/Kosrae"
- },
- {
- "name": "Pacific/Norfolk (GMT+11:00)",
- "value": "Pacific/Norfolk"
- },
- {
- "name": "Pacific/Noumea (GMT+11:00)",
- "value": "Pacific/Noumea"
- },
- {
- "name": "Pacific/Pohnpei (GMT+11:00)",
- "value": "Pacific/Pohnpei"
- },
- {
- "name": "Antarctica/McMurdo (GMT+12:00)",
- "value": "Antarctica/McMurdo"
- },
- {
- "name": "Asia/Anadyr (GMT+12:00)",
- "value": "Asia/Anadyr"
- },
- {
- "name": "Asia/Kamchatka (GMT+12:00)",
- "value": "Asia/Kamchatka"
- },
- {
- "name": "Pacific/Auckland (GMT+12:00)",
- "value": "Pacific/Auckland"
- },
- {
- "name": "Pacific/Fiji (GMT+12:00)",
- "value": "Pacific/Fiji"
- },
- {
- "name": "Pacific/Funafuti (GMT+12:00)",
- "value": "Pacific/Funafuti"
- },
- {
- "name": "Pacific/Kwajalein (GMT+12:00)",
- "value": "Pacific/Kwajalein"
- },
- {
- "name": "Pacific/Majuro (GMT+12:00)",
- "value": "Pacific/Majuro"
- },
- {
- "name": "Pacific/Nauru (GMT+12:00)",
- "value": "Pacific/Nauru"
- },
- {
- "name": "Pacific/Tarawa (GMT+12:00)",
- "value": "Pacific/Tarawa"
- },
- {
- "name": "Pacific/Wake (GMT+12:00)",
- "value": "Pacific/Wake"
- },
- {
- "name": "Pacific/Wallis (GMT+12:00)",
- "value": "Pacific/Wallis"
- },
- {
- "name": "Pacific/Chatham (GMT+12:45)",
- "value": "Pacific/Chatham"
- },
- {
- "name": "Pacific/Apia (GMT+13:00)",
- "value": "Pacific/Apia"
- },
- {
- "name": "Pacific/Enderbury (GMT+13:00)",
- "value": "Pacific/Enderbury"
- },
- {
- "name": "Pacific/Fakaofo (GMT+13:00)",
- "value": "Pacific/Fakaofo"
- },
- {
- "name": "Pacific/Tongatapu (GMT+13:00)",
- "value": "Pacific/Tongatapu"
- },
- {
- "name": "Pacific/Kiritimati (GMT+14:00)",
- "value": "Pacific/Kiritimati"
- }
-]
diff --git a/app/constant/timezones.json b/app/constant/timezones.json
new file mode 100644
index 0000000..4f9f0bc
--- /dev/null
+++ b/app/constant/timezones.json
@@ -0,0 +1,426 @@
+{
+ "Pacific/Midway": "Pacific/Midway (GMT-11:00)",
+ "Pacific/Niue": "Pacific/Niue (GMT-11:00)",
+ "Pacific/Pago_Pago": "Pacific/Pago_Pago (GMT-11:00)",
+ "America/Adak": "America/Adak (GMT-10:00)",
+ "Pacific/Honolulu": "Pacific/Honolulu (GMT-10:00)",
+ "Pacific/Rarotonga": "Pacific/Rarotonga (GMT-10:00)",
+ "Pacific/Tahiti": "Pacific/Tahiti (GMT-10:00)",
+ "Pacific/Marquesas": "Pacific/Marquesas (GMT-09:30)",
+ "America/Anchorage": "America/Anchorage (GMT-09:00)",
+ "America/Juneau": "America/Juneau (GMT-09:00)",
+ "America/Metlakatla": "America/Metlakatla (GMT-09:00)",
+ "America/Nome": "America/Nome (GMT-09:00)",
+ "America/Sitka": "America/Sitka (GMT-09:00)",
+ "America/Yakutat": "America/Yakutat (GMT-09:00)",
+ "Pacific/Gambier": "Pacific/Gambier (GMT-09:00)",
+ "America/Los_Angeles": "America/Los_Angeles (GMT-08:00)",
+ "America/Tijuana": "America/Tijuana (GMT-08:00)",
+ "America/Vancouver": "America/Vancouver (GMT-08:00)",
+ "Pacific/Pitcairn": "Pacific/Pitcairn (GMT-08:00)",
+ "America/Boise": "America/Boise (GMT-07:00)",
+ "America/Cambridge_Bay": "America/Cambridge_Bay (GMT-07:00)",
+ "America/Chihuahua": "America/Chihuahua (GMT-07:00)",
+ "America/Creston": "America/Creston (GMT-07:00)",
+ "America/Dawson": "America/Dawson (GMT-07:00)",
+ "America/Dawson_Creek": "America/Dawson_Creek (GMT-07:00)",
+ "America/Denver": "America/Denver (GMT-07:00)",
+ "America/Edmonton": "America/Edmonton (GMT-07:00)",
+ "America/Fort_Nelson": "America/Fort_Nelson (GMT-07:00)",
+ "America/Hermosillo": "America/Hermosillo (GMT-07:00)",
+ "America/Inuvik": "America/Inuvik (GMT-07:00)",
+ "America/Mazatlan": "America/Mazatlan (GMT-07:00)",
+ "America/Ojinaga": "America/Ojinaga (GMT-07:00)",
+ "America/Phoenix": "America/Phoenix (GMT-07:00)",
+ "America/Whitehorse": "America/Whitehorse (GMT-07:00)",
+ "America/Yellowknife": "America/Yellowknife (GMT-07:00)",
+ "America/Bahia_Banderas": "America/Bahia_Banderas (GMT-06:00)",
+ "America/Belize": "America/Belize (GMT-06:00)",
+ "America/Chicago": "America/Chicago (GMT-06:00)",
+ "America/Costa_Rica": "America/Costa_Rica (GMT-06:00)",
+ "America/El_Salvador": "America/El_Salvador (GMT-06:00)",
+ "America/Guatemala": "America/Guatemala (GMT-06:00)",
+ "America/Indiana/Knox": "America/Indiana/Knox (GMT-06:00)",
+ "America/Indiana/Tell_City": "America/Indiana/Tell_City (GMT-06:00)",
+ "America/Managua": "America/Managua (GMT-06:00)",
+ "America/Matamoros": "America/Matamoros (GMT-06:00)",
+ "America/Menominee": "America/Menominee (GMT-06:00)",
+ "America/Merida": "America/Merida (GMT-06:00)",
+ "America/Mexico_City": "America/Mexico_City (GMT-06:00)",
+ "America/Monterrey": "America/Monterrey (GMT-06:00)",
+ "America/North_Dakota/Beulah": "America/North_Dakota/Beulah (GMT-06:00)",
+ "America/North_Dakota/Center": "America/North_Dakota/Center (GMT-06:00)",
+ "America/North_Dakota/New_Salem": "America/North_Dakota/New_Salem (GMT-06:00)",
+ "America/Rainy_River": "America/Rainy_River (GMT-06:00)",
+ "America/Rankin_Inlet": "America/Rankin_Inlet (GMT-06:00)",
+ "America/Regina": "America/Regina (GMT-06:00)",
+ "America/Resolute": "America/Resolute (GMT-06:00)",
+ "America/Swift_Current": "America/Swift_Current (GMT-06:00)",
+ "America/Tegucigalpa": "America/Tegucigalpa (GMT-06:00)",
+ "America/Winnipeg": "America/Winnipeg (GMT-06:00)",
+ "Pacific/Easter": "Pacific/Easter (GMT-06:00)",
+ "Pacific/Galapagos": "Pacific/Galapagos (GMT-06:00)",
+ "America/Atikokan": "America/Atikokan (GMT-05:00)",
+ "America/Bogota": "America/Bogota (GMT-05:00)",
+ "America/Cancun": "America/Cancun (GMT-05:00)",
+ "America/Cayman": "America/Cayman (GMT-05:00)",
+ "America/Detroit": "America/Detroit (GMT-05:00)",
+ "America/Eirunepe": "America/Eirunepe (GMT-05:00)",
+ "America/Grand_Turk": "America/Grand_Turk (GMT-05:00)",
+ "America/Guayaquil": "America/Guayaquil (GMT-05:00)",
+ "America/Havana": "America/Havana (GMT-05:00)",
+ "America/Indiana/Indianapolis": "America/Indiana/Indianapolis (GMT-05:00)",
+ "America/Indiana/Marengo": "America/Indiana/Marengo (GMT-05:00)",
+ "America/Indiana/Petersburg": "America/Indiana/Petersburg (GMT-05:00)",
+ "America/Indiana/Vevay": "America/Indiana/Vevay (GMT-05:00)",
+ "America/Indiana/Vincennes": "America/Indiana/Vincennes (GMT-05:00)",
+ "America/Indiana/Winamac": "America/Indiana/Winamac (GMT-05:00)",
+ "America/Iqaluit": "America/Iqaluit (GMT-05:00)",
+ "America/Jamaica": "America/Jamaica (GMT-05:00)",
+ "America/Kentucky/Louisville": "America/Kentucky/Louisville (GMT-05:00)",
+ "America/Kentucky/Monticello": "America/Kentucky/Monticello (GMT-05:00)",
+ "America/Lima": "America/Lima (GMT-05:00)",
+ "America/Nassau": "America/Nassau (GMT-05:00)",
+ "America/New_York": "America/New_York (GMT-05:00)",
+ "America/Nipigon": "America/Nipigon (GMT-05:00)",
+ "America/Panama": "America/Panama (GMT-05:00)",
+ "America/Pangnirtung": "America/Pangnirtung (GMT-05:00)",
+ "America/Port-au-Prince": "America/Port-au-Prince (GMT-05:00)",
+ "America/Rio_Branco": "America/Rio_Branco (GMT-05:00)",
+ "America/Thunder_Bay": "America/Thunder_Bay (GMT-05:00)",
+ "America/Toronto": "America/Toronto (GMT-05:00)",
+ "America/Anguilla": "America/Anguilla (GMT-04:00)",
+ "America/Antigua": "America/Antigua (GMT-04:00)",
+ "America/Aruba": "America/Aruba (GMT-04:00)",
+ "America/Asuncion": "America/Asuncion (GMT-04:00)",
+ "America/Barbados": "America/Barbados (GMT-04:00)",
+ "America/Blanc-Sablon": "America/Blanc-Sablon (GMT-04:00)",
+ "America/Boa_Vista": "America/Boa_Vista (GMT-04:00)",
+ "America/Campo_Grande": "America/Campo_Grande (GMT-04:00)",
+ "America/Caracas": "America/Caracas (GMT-04:00)",
+ "America/Cuiaba": "America/Cuiaba (GMT-04:00)",
+ "America/Curacao": "America/Curacao (GMT-04:00)",
+ "America/Dominica": "America/Dominica (GMT-04:00)",
+ "America/Glace_Bay": "America/Glace_Bay (GMT-04:00)",
+ "America/Goose_Bay": "America/Goose_Bay (GMT-04:00)",
+ "America/Grenada": "America/Grenada (GMT-04:00)",
+ "America/Guadeloupe": "America/Guadeloupe (GMT-04:00)",
+ "America/Guyana": "America/Guyana (GMT-04:00)",
+ "America/Halifax": "America/Halifax (GMT-04:00)",
+ "America/Kralendijk": "America/Kralendijk (GMT-04:00)",
+ "America/La_Paz": "America/La_Paz (GMT-04:00)",
+ "America/Lower_Princes": "America/Lower_Princes (GMT-04:00)",
+ "America/Manaus": "America/Manaus (GMT-04:00)",
+ "America/Marigot": "America/Marigot (GMT-04:00)",
+ "America/Martinique": "America/Martinique (GMT-04:00)",
+ "America/Moncton": "America/Moncton (GMT-04:00)",
+ "America/Montserrat": "America/Montserrat (GMT-04:00)",
+ "America/Porto_Velho": "America/Porto_Velho (GMT-04:00)",
+ "America/Port_of_Spain": "America/Port_of_Spain (GMT-04:00)",
+ "America/Puerto_Rico": "America/Puerto_Rico (GMT-04:00)",
+ "America/Santiago": "America/Santiago (GMT-04:00)",
+ "America/Santo_Domingo": "America/Santo_Domingo (GMT-04:00)",
+ "America/St_Barthelemy": "America/St_Barthelemy (GMT-04:00)",
+ "America/St_Kitts": "America/St_Kitts (GMT-04:00)",
+ "America/St_Lucia": "America/St_Lucia (GMT-04:00)",
+ "America/St_Thomas": "America/St_Thomas (GMT-04:00)",
+ "America/St_Vincent": "America/St_Vincent (GMT-04:00)",
+ "America/Thule": "America/Thule (GMT-04:00)",
+ "America/Tortola": "America/Tortola (GMT-04:00)",
+ "Atlantic/Bermuda": "Atlantic/Bermuda (GMT-04:00)",
+ "America/St_Johns": "America/St_Johns (GMT-03:30)",
+ "America/Araguaina": "America/Araguaina (GMT-03:00)",
+ "America/Argentina/Buenos_Aires": "America/Argentina/Buenos_Aires (GMT-03:00)",
+ "America/Argentina/Catamarca": "America/Argentina/Catamarca (GMT-03:00)",
+ "America/Argentina/Cordoba": "America/Argentina/Cordoba (GMT-03:00)",
+ "America/Argentina/Jujuy": "America/Argentina/Jujuy (GMT-03:00)",
+ "America/Argentina/La_Rioja": "America/Argentina/La_Rioja (GMT-03:00)",
+ "America/Argentina/Mendoza": "America/Argentina/Mendoza (GMT-03:00)",
+ "America/Argentina/Rio_Gallegos": "America/Argentina/Rio_Gallegos (GMT-03:00)",
+ "America/Argentina/Salta": "America/Argentina/Salta (GMT-03:00)",
+ "America/Argentina/San_Juan": "America/Argentina/San_Juan (GMT-03:00)",
+ "America/Argentina/San_Luis": "America/Argentina/San_Luis (GMT-03:00)",
+ "America/Argentina/Tucuman": "America/Argentina/Tucuman (GMT-03:00)",
+ "America/Argentina/Ushuaia": "America/Argentina/Ushuaia (GMT-03:00)",
+ "America/Bahia": "America/Bahia (GMT-03:00)",
+ "America/Belem": "America/Belem (GMT-03:00)",
+ "America/Cayenne": "America/Cayenne (GMT-03:00)",
+ "America/Fortaleza": "America/Fortaleza (GMT-03:00)",
+ "America/Godthab": "America/Godthab (GMT-03:00)",
+ "America/Maceio": "America/Maceio (GMT-03:00)",
+ "America/Miquelon": "America/Miquelon (GMT-03:00)",
+ "America/Montevideo": "America/Montevideo (GMT-03:00)",
+ "America/Paramaribo": "America/Paramaribo (GMT-03:00)",
+ "America/Punta_Arenas": "America/Punta_Arenas (GMT-03:00)",
+ "America/Recife": "America/Recife (GMT-03:00)",
+ "America/Santarem": "America/Santarem (GMT-03:00)",
+ "America/Sao_Paulo": "America/Sao_Paulo (GMT-03:00)",
+ "Antarctica/Palmer": "Antarctica/Palmer (GMT-03:00)",
+ "Antarctica/Rothera": "Antarctica/Rothera (GMT-03:00)",
+ "Atlantic/Stanley": "Atlantic/Stanley (GMT-03:00)",
+ "America/Noronha": "America/Noronha (GMT-02:00)",
+ "Atlantic/South_Georgia": "Atlantic/South_Georgia (GMT-02:00)",
+ "America/Scoresbysund": "America/Scoresbysund (GMT-01:00)",
+ "Atlantic/Azores": "Atlantic/Azores (GMT-01:00)",
+ "Atlantic/Cape_Verde": "Atlantic/Cape_Verde (GMT-01:00)",
+ "Africa/Abidjan": "Africa/Abidjan (GMT+00:00)",
+ "Africa/Accra": "Africa/Accra (GMT+00:00)",
+ "Africa/Bamako": "Africa/Bamako (GMT+00:00)",
+ "Africa/Banjul": "Africa/Banjul (GMT+00:00)",
+ "Africa/Bissau": "Africa/Bissau (GMT+00:00)",
+ "Africa/Casablanca": "Africa/Casablanca (GMT+00:00)",
+ "Africa/Conakry": "Africa/Conakry (GMT+00:00)",
+ "Africa/Dakar": "Africa/Dakar (GMT+00:00)",
+ "Africa/El_Aaiun": "Africa/El_Aaiun (GMT+00:00)",
+ "Africa/Freetown": "Africa/Freetown (GMT+00:00)",
+ "Africa/Lome": "Africa/Lome (GMT+00:00)",
+ "Africa/Monrovia": "Africa/Monrovia (GMT+00:00)",
+ "Africa/Nouakchott": "Africa/Nouakchott (GMT+00:00)",
+ "Africa/Ouagadougou": "Africa/Ouagadougou (GMT+00:00)",
+ "Africa/Sao_Tome": "Africa/Sao_Tome (GMT+00:00)",
+ "America/Danmarkshavn": "America/Danmarkshavn (GMT+00:00)",
+ "Antarctica/Troll": "Antarctica/Troll (GMT+00:00)",
+ "Atlantic/Canary": "Atlantic/Canary (GMT+00:00)",
+ "Atlantic/Faroe": "Atlantic/Faroe (GMT+00:00)",
+ "Atlantic/Madeira": "Atlantic/Madeira (GMT+00:00)",
+ "Atlantic/Reykjavik": "Atlantic/Reykjavik (GMT+00:00)",
+ "Atlantic/St_Helena": "Atlantic/St_Helena (GMT+00:00)",
+ "Europe/Dublin": "Europe/Dublin (GMT+00:00)",
+ "Europe/Guernsey": "Europe/Guernsey (GMT+00:00)",
+ "Europe/Isle_of_Man": "Europe/Isle_of_Man (GMT+00:00)",
+ "Europe/Jersey": "Europe/Jersey (GMT+00:00)",
+ "Europe/Lisbon": "Europe/Lisbon (GMT+00:00)",
+ "Europe/London": "Europe/London (GMT+00:00)",
+ "Africa/Algiers": "Africa/Algiers (GMT+01:00)",
+ "Africa/Bangui": "Africa/Bangui (GMT+01:00)",
+ "Africa/Brazzaville": "Africa/Brazzaville (GMT+01:00)",
+ "Africa/Ceuta": "Africa/Ceuta (GMT+01:00)",
+ "Africa/Douala": "Africa/Douala (GMT+01:00)",
+ "Africa/Kinshasa": "Africa/Kinshasa (GMT+01:00)",
+ "Africa/Lagos": "Africa/Lagos (GMT+01:00)",
+ "Africa/Libreville": "Africa/Libreville (GMT+01:00)",
+ "Africa/Luanda": "Africa/Luanda (GMT+01:00)",
+ "Africa/Malabo": "Africa/Malabo (GMT+01:00)",
+ "Africa/Ndjamena": "Africa/Ndjamena (GMT+01:00)",
+ "Africa/Niamey": "Africa/Niamey (GMT+01:00)",
+ "Africa/Porto-Novo": "Africa/Porto-Novo (GMT+01:00)",
+ "Africa/Tunis": "Africa/Tunis (GMT+01:00)",
+ "Africa/Windhoek": "Africa/Windhoek (GMT+01:00)",
+ "Arctic/Longyearbyen": "Arctic/Longyearbyen (GMT+01:00)",
+ "Europe/Amsterdam": "Europe/Amsterdam (GMT+01:00)",
+ "Europe/Andorra": "Europe/Andorra (GMT+01:00)",
+ "Europe/Belgrade": "Europe/Belgrade (GMT+01:00)",
+ "Europe/Berlin": "Europe/Berlin (GMT+01:00)",
+ "Europe/Bratislava": "Europe/Bratislava (GMT+01:00)",
+ "Europe/Brussels": "Europe/Brussels (GMT+01:00)",
+ "Europe/Budapest": "Europe/Budapest (GMT+01:00)",
+ "Europe/Copenhagen": "Europe/Copenhagen (GMT+01:00)",
+ "Europe/Gibraltar": "Europe/Gibraltar (GMT+01:00)",
+ "Europe/Ljubljana": "Europe/Ljubljana (GMT+01:00)",
+ "Europe/Luxembourg": "Europe/Luxembourg (GMT+01:00)",
+ "Europe/Madrid": "Europe/Madrid (GMT+01:00)",
+ "Europe/Malta": "Europe/Malta (GMT+01:00)",
+ "Europe/Monaco": "Europe/Monaco (GMT+01:00)",
+ "Europe/Oslo": "Europe/Oslo (GMT+01:00)",
+ "Europe/Paris": "Europe/Paris (GMT+01:00)",
+ "Europe/Podgorica": "Europe/Podgorica (GMT+01:00)",
+ "Europe/Prague": "Europe/Prague (GMT+01:00)",
+ "Europe/Rome": "Europe/Rome (GMT+01:00)",
+ "Europe/San_Marino": "Europe/San_Marino (GMT+01:00)",
+ "Europe/Sarajevo": "Europe/Sarajevo (GMT+01:00)",
+ "Europe/Skopje": "Europe/Skopje (GMT+01:00)",
+ "Europe/Stockholm": "Europe/Stockholm (GMT+01:00)",
+ "Europe/Tirane": "Europe/Tirane (GMT+01:00)",
+ "Europe/Vaduz": "Europe/Vaduz (GMT+01:00)",
+ "Europe/Vatican": "Europe/Vatican (GMT+01:00)",
+ "Europe/Vienna": "Europe/Vienna (GMT+01:00)",
+ "Europe/Warsaw": "Europe/Warsaw (GMT+01:00)",
+ "Europe/Zagreb": "Europe/Zagreb (GMT+01:00)",
+ "Europe/Zurich": "Europe/Zurich (GMT+01:00)",
+ "Africa/Blantyre": "Africa/Blantyre (GMT+02:00)",
+ "Africa/Bujumbura": "Africa/Bujumbura (GMT+02:00)",
+ "Africa/Cairo": "Africa/Cairo (GMT+02:00)",
+ "Africa/Gaborone": "Africa/Gaborone (GMT+02:00)",
+ "Africa/Harare": "Africa/Harare (GMT+02:00)",
+ "Africa/Johannesburg": "Africa/Johannesburg (GMT+02:00)",
+ "Africa/Juba": "Africa/Juba (GMT+02:00)",
+ "Africa/Khartoum": "Africa/Khartoum (GMT+02:00)",
+ "Africa/Kigali": "Africa/Kigali (GMT+02:00)",
+ "Africa/Lubumbashi": "Africa/Lubumbashi (GMT+02:00)",
+ "Africa/Lusaka": "Africa/Lusaka (GMT+02:00)",
+ "Africa/Maputo": "Africa/Maputo (GMT+02:00)",
+ "Africa/Maseru": "Africa/Maseru (GMT+02:00)",
+ "Africa/Mbabane": "Africa/Mbabane (GMT+02:00)",
+ "Africa/Tripoli": "Africa/Tripoli (GMT+02:00)",
+ "Asia/Amman": "Asia/Amman (GMT+02:00)",
+ "Asia/Beirut": "Asia/Beirut (GMT+02:00)",
+ "Asia/Damascus": "Asia/Damascus (GMT+02:00)",
+ "Asia/Famagusta": "Asia/Famagusta (GMT+02:00)",
+ "Asia/Gaza": "Asia/Gaza (GMT+02:00)",
+ "Asia/Hebron": "Asia/Hebron (GMT+02:00)",
+ "Asia/Jerusalem": "Asia/Jerusalem (GMT+02:00)",
+ "Asia/Nicosia": "Asia/Nicosia (GMT+02:00)",
+ "Europe/Athens": "Europe/Athens (GMT+02:00)",
+ "Europe/Bucharest": "Europe/Bucharest (GMT+02:00)",
+ "Europe/Chisinau": "Europe/Chisinau (GMT+02:00)",
+ "Europe/Helsinki": "Europe/Helsinki (GMT+02:00)",
+ "Europe/Kaliningrad": "Europe/Kaliningrad (GMT+02:00)",
+ "Europe/Kyiv": "Europe/Kyiv (GMT+02:00)",
+ "Europe/Mariehamn": "Europe/Mariehamn (GMT+02:00)",
+ "Europe/Riga": "Europe/Riga (GMT+02:00)",
+ "Europe/Sofia": "Europe/Sofia (GMT+02:00)",
+ "Europe/Tallinn": "Europe/Tallinn (GMT+02:00)",
+ "Europe/Uzhgorod": "Europe/Uzhgorod (GMT+02:00)",
+ "Europe/Vilnius": "Europe/Vilnius (GMT+02:00)",
+ "Europe/Zaporozhye": "Europe/Zaporozhye (GMT+02:00)",
+ "Africa/Addis_Ababa": "Africa/Addis_Ababa (GMT+03:00)",
+ "Africa/Asmara": "Africa/Asmara (GMT+03:00)",
+ "Africa/Dar_es_Salaam": "Africa/Dar_es_Salaam (GMT+03:00)",
+ "Africa/Djibouti": "Africa/Djibouti (GMT+03:00)",
+ "Africa/Kampala": "Africa/Kampala (GMT+03:00)",
+ "Africa/Mogadishu": "Africa/Mogadishu (GMT+03:00)",
+ "Africa/Nairobi": "Africa/Nairobi (GMT+03:00)",
+ "Antarctica/Syowa": "Antarctica/Syowa (GMT+03:00)",
+ "Asia/Aden": "Asia/Aden (GMT+03:00)",
+ "Asia/Baghdad": "Asia/Baghdad (GMT+03:00)",
+ "Asia/Bahrain": "Asia/Bahrain (GMT+03:00)",
+ "Asia/Kuwait": "Asia/Kuwait (GMT+03:00)",
+ "Asia/Qatar": "Asia/Qatar (GMT+03:00)",
+ "Asia/Riyadh": "Asia/Riyadh (GMT+03:00)",
+ "Europe/Istanbul": "Europe/Istanbul (GMT+03:00)",
+ "Europe/Kirov": "Europe/Kirov (GMT+03:00)",
+ "Europe/Minsk": "Europe/Minsk (GMT+03:00)",
+ "Europe/Moscow": "Europe/Moscow (GMT+03:00)",
+ "Europe/Simferopol": "Europe/Simferopol (GMT+03:00)",
+ "Europe/Volgograd": "Europe/Volgograd (GMT+03:00)",
+ "Indian/Antananarivo": "Indian/Antananarivo (GMT+03:00)",
+ "Indian/Comoro": "Indian/Comoro (GMT+03:00)",
+ "Indian/Mayotte": "Indian/Mayotte (GMT+03:00)",
+ "Asia/Tehran": "Asia/Tehran (GMT+03:30)",
+ "Asia/Baku": "Asia/Baku (GMT+04:00)",
+ "Asia/Dubai": "Asia/Dubai (GMT+04:00)",
+ "Asia/Muscat": "Asia/Muscat (GMT+04:00)",
+ "Asia/Tbilisi": "Asia/Tbilisi (GMT+04:00)",
+ "Asia/Yerevan": "Asia/Yerevan (GMT+04:00)",
+ "Europe/Astrakhan": "Europe/Astrakhan (GMT+04:00)",
+ "Europe/Samara": "Europe/Samara (GMT+04:00)",
+ "Europe/Saratov": "Europe/Saratov (GMT+04:00)",
+ "Europe/Ulyanovsk": "Europe/Ulyanovsk (GMT+04:00)",
+ "Indian/Mahe": "Indian/Mahe (GMT+04:00)",
+ "Indian/Mauritius": "Indian/Mauritius (GMT+04:00)",
+ "Indian/Reunion": "Indian/Reunion (GMT+04:00)",
+ "Asia/Kabul": "Asia/Kabul (GMT+04:30)",
+ "Antarctica/Mawson": "Antarctica/Mawson (GMT+05:00)",
+ "Asia/Aqtau": "Asia/Aqtau (GMT+05:00)",
+ "Asia/Aqtobe": "Asia/Aqtobe (GMT+05:00)",
+ "Asia/Ashgabat": "Asia/Ashgabat (GMT+05:00)",
+ "Asia/Atyrau": "Asia/Atyrau (GMT+05:00)",
+ "Asia/Dushanbe": "Asia/Dushanbe (GMT+05:00)",
+ "Asia/Karachi": "Asia/Karachi (GMT+05:00)",
+ "Asia/Oral": "Asia/Oral (GMT+05:00)",
+ "Asia/Qyzylorda": "Asia/Qyzylorda (GMT+05:00)",
+ "Asia/Samarkand": "Asia/Samarkand (GMT+05:00)",
+ "Asia/Tashkent": "Asia/Tashkent (GMT+05:00)",
+ "Asia/Yekaterinburg": "Asia/Yekaterinburg (GMT+05:00)",
+ "Indian/Kerguelen": "Indian/Kerguelen (GMT+05:00)",
+ "Indian/Maldives": "Indian/Maldives (GMT+05:00)",
+ "Asia/Colombo": "Asia/Colombo (GMT+05:30)",
+ "Asia/Kolkata": "Asia/Kolkata (GMT+05:30)",
+ "Asia/Kathmandu": "Asia/Kathmandu (GMT+05:45)",
+ "Antarctica/Vostok": "Antarctica/Vostok (GMT+06:00)",
+ "Asia/Almaty": "Asia/Almaty (GMT+06:00)",
+ "Asia/Bishkek": "Asia/Bishkek (GMT+06:00)",
+ "Asia/Dhaka": "Asia/Dhaka (GMT+06:00)",
+ "Asia/Omsk": "Asia/Omsk (GMT+06:00)",
+ "Asia/Qostanay": "Asia/Qostanay (GMT+06:00)",
+ "Asia/Thimphu": "Asia/Thimphu (GMT+06:00)",
+ "Asia/Urumqi": "Asia/Urumqi (GMT+06:00)",
+ "Indian/Chagos": "Indian/Chagos (GMT+06:00)",
+ "Asia/Yangon": "Asia/Yangon (GMT+06:30)",
+ "Indian/Cocos": "Indian/Cocos (GMT+06:30)",
+ "Antarctica/Davis": "Antarctica/Davis (GMT+07:00)",
+ "Asia/Bangkok": "Asia/Bangkok (GMT+07:00)",
+ "Asia/Barnaul": "Asia/Barnaul (GMT+07:00)",
+ "Asia/Hovd": "Asia/Hovd (GMT+07:00)",
+ "Asia/Ho_Chi_Minh": "Asia/Ho_Chi_Minh (GMT+07:00)",
+ "Asia/Jakarta": "Asia/Jakarta (GMT+07:00)",
+ "Asia/Krasnoyarsk": "Asia/Krasnoyarsk (GMT+07:00)",
+ "Asia/Novokuznetsk": "Asia/Novokuznetsk (GMT+07:00)",
+ "Asia/Novosibirsk": "Asia/Novosibirsk (GMT+07:00)",
+ "Asia/Phnom_Penh": "Asia/Phnom_Penh (GMT+07:00)",
+ "Asia/Pontianak": "Asia/Pontianak (GMT+07:00)",
+ "Asia/Tomsk": "Asia/Tomsk (GMT+07:00)",
+ "Asia/Vientiane": "Asia/Vientiane (GMT+07:00)",
+ "Indian/Christmas": "Indian/Christmas (GMT+07:00)",
+ "Asia/Brunei": "Asia/Brunei (GMT+08:00)",
+ "Asia/Choibalsan": "Asia/Choibalsan (GMT+08:00)",
+ "Asia/Hong_Kong": "Asia/Hong_Kong (GMT+08:00)",
+ "Asia/Irkutsk": "Asia/Irkutsk (GMT+08:00)",
+ "Asia/Kuala_Lumpur": "Asia/Kuala_Lumpur (GMT+08:00)",
+ "Asia/Kuching": "Asia/Kuching (GMT+08:00)",
+ "Asia/Macau": "Asia/Macau (GMT+08:00)",
+ "Asia/Makassar": "Asia/Makassar (GMT+08:00)",
+ "Asia/Manila": "Asia/Manila (GMT+08:00)",
+ "Asia/Shanghai": "Asia/Shanghai (GMT+08:00)",
+ "Asia/Singapore": "Asia/Singapore (GMT+08:00)",
+ "Asia/Taipei": "Asia/Taipei (GMT+08:00)",
+ "Asia/Ulaanbaatar": "Asia/Ulaanbaatar (GMT+08:00)",
+ "Australia/Perth": "Australia/Perth (GMT+08:00)",
+ "Australia/Eucla": "Australia/Eucla (GMT+08:45)",
+ "Asia/Chita": "Asia/Chita (GMT+09:00)",
+ "Asia/Dili": "Asia/Dili (GMT+09:00)",
+ "Asia/Jayapura": "Asia/Jayapura (GMT+09:00)",
+ "Asia/Khandyga": "Asia/Khandyga (GMT+09:00)",
+ "Asia/Pyongyang": "Asia/Pyongyang (GMT+09:00)",
+ "Asia/Seoul": "Asia/Seoul (GMT+09:00)",
+ "Asia/Tokyo": "Asia/Tokyo (GMT+09:00)",
+ "Asia/Yakutsk": "Asia/Yakutsk (GMT+09:00)",
+ "Pacific/Palau": "Pacific/Palau (GMT+09:00)",
+ "Australia/Adelaide": "Australia/Adelaide (GMT+09:30)",
+ "Australia/Broken_Hill": "Australia/Broken_Hill (GMT+09:30)",
+ "Australia/Darwin": "Australia/Darwin (GMT+09:30)",
+ "Antarctica/DumontDUrville": "Antarctica/DumontDUrville (GMT+10:00)",
+ "Antarctica/Macquarie": "Antarctica/Macquarie (GMT+10:00)",
+ "Asia/Ust-Nera": "Asia/Ust-Nera (GMT+10:00)",
+ "Asia/Vladivostok": "Asia/Vladivostok (GMT+10:00)",
+ "Australia/Brisbane": "Australia/Brisbane (GMT+10:00)",
+ "Australia/Currie": "Australia/Currie (GMT+10:00)",
+ "Australia/Hobart": "Australia/Hobart (GMT+10:00)",
+ "Australia/Lindeman": "Australia/Lindeman (GMT+10:00)",
+ "Australia/Melbourne": "Australia/Melbourne (GMT+10:00)",
+ "Australia/Sydney": "Australia/Sydney (GMT+10:00)",
+ "Pacific/Chuuk": "Pacific/Chuuk (GMT+10:00)",
+ "Pacific/Guam": "Pacific/Guam (GMT+10:00)",
+ "Pacific/Port_Moresby": "Pacific/Port_Moresby (GMT+10:00)",
+ "Pacific/Saipan": "Pacific/Saipan (GMT+10:00)",
+ "Australia/Lord_Howe": "Australia/Lord_Howe (GMT+10:30)",
+ "Antarctica/Casey": "Antarctica/Casey (GMT+11:00)",
+ "Asia/Magadan": "Asia/Magadan (GMT+11:00)",
+ "Asia/Sakhalin": "Asia/Sakhalin (GMT+11:00)",
+ "Asia/Srednekolymsk": "Asia/Srednekolymsk (GMT+11:00)",
+ "Pacific/Bougainville": "Pacific/Bougainville (GMT+11:00)",
+ "Pacific/Efate": "Pacific/Efate (GMT+11:00)",
+ "Pacific/Guadalcanal": "Pacific/Guadalcanal (GMT+11:00)",
+ "Pacific/Kosrae": "Pacific/Kosrae (GMT+11:00)",
+ "Pacific/Norfolk": "Pacific/Norfolk (GMT+11:00)",
+ "Pacific/Noumea": "Pacific/Noumea (GMT+11:00)",
+ "Pacific/Pohnpei": "Pacific/Pohnpei (GMT+11:00)",
+ "Antarctica/McMurdo": "Antarctica/McMurdo (GMT+12:00)",
+ "Asia/Anadyr": "Asia/Anadyr (GMT+12:00)",
+ "Asia/Kamchatka": "Asia/Kamchatka (GMT+12:00)",
+ "Pacific/Auckland": "Pacific/Auckland (GMT+12:00)",
+ "Pacific/Fiji": "Pacific/Fiji (GMT+12:00)",
+ "Pacific/Funafuti": "Pacific/Funafuti (GMT+12:00)",
+ "Pacific/Kwajalein": "Pacific/Kwajalein (GMT+12:00)",
+ "Pacific/Majuro": "Pacific/Majuro (GMT+12:00)",
+ "Pacific/Nauru": "Pacific/Nauru (GMT+12:00)",
+ "Pacific/Tarawa": "Pacific/Tarawa (GMT+12:00)",
+ "Pacific/Wake": "Pacific/Wake (GMT+12:00)",
+ "Pacific/Wallis": "Pacific/Wallis (GMT+12:00)",
+ "Pacific/Chatham": "Pacific/Chatham (GMT+12:45)",
+ "Pacific/Apia": "Pacific/Apia (GMT+13:00)",
+ "Pacific/Enderbury": "Pacific/Enderbury (GMT+13:00)",
+ "Pacific/Fakaofo": "Pacific/Fakaofo (GMT+13:00)",
+ "Pacific/Tongatapu": "Pacific/Tongatapu (GMT+13:00)",
+ "Pacific/Kiritimati": "Pacific/Kiritimati (GMT+14:00)"
+}
diff --git a/app/context/user.js b/app/context/user.js
index c07268a..2309a27 100644
--- a/app/context/user.js
+++ b/app/context/user.js
@@ -6,6 +6,7 @@ export default class UserStore {
makeObservable(this, {
user: observable,
setUser: action,
+ updateUsingKey: action,
updateUser: action,
});
}
@@ -17,4 +18,8 @@ export default class UserStore {
updateUser = (data) => {
this.user = data;
};
+
+ updateUsingKey = (key, value) => {
+ this.user[key] = value;
+ };
}
diff --git a/app/handlers/login.js b/app/handlers/login.js
index 6fe47d3..a445bcb 100644
--- a/app/handlers/login.js
+++ b/app/handlers/login.js
@@ -22,6 +22,8 @@ const handleLogin = async (inputs, setErrors, navigate) => {
return navigate('/');
}
} catch (error) {
+ console.log(error);
+
if (error.response?.status === 418) {
return navigate('/verify');
}
diff --git a/app/handlers/monitor.js b/app/handlers/monitor.js
index 378ddd5..9831d7d 100644
--- a/app/handlers/monitor.js
+++ b/app/handlers/monitor.js
@@ -36,6 +36,7 @@ const handleMonitor = async (form, isEdit, closeModal, setMonitor) => {
toast.success(`Monitor been ${isEdit ? 'added' : 'edited'} successfully`);
return closeModal();
} catch (error) {
+ console.log(error);
toast.error('Something went wrong, please try again later.');
}
};
diff --git a/app/handlers/register.js b/app/handlers/register.js
index afedf8e..2732f56 100644
--- a/app/handlers/register.js
+++ b/app/handlers/register.js
@@ -33,6 +33,7 @@ const handleRegister = async (inputs, setErrors, setPage, navigate) => {
setPage('verify');
} catch (error) {
+ console.log(error);
if (error?.response?.data?.message) {
return setErrors(error?.response?.data?.message);
}
diff --git a/app/handlers/settings/account/password.js b/app/handlers/settings/account/password.js
new file mode 100644
index 0000000..1b6ebed
--- /dev/null
+++ b/app/handlers/settings/account/password.js
@@ -0,0 +1,46 @@
+import { toast } from 'sonner';
+
+import * as validators from '../../../utils/validators';
+import { createPostRequest } from '../../../services/axios';
+
+const handleChangePassword = async ({
+ currentPassword,
+ newPassword,
+ repeatPassword,
+ handleErrors,
+ closeModal,
+}) => {
+ try {
+ if (newPassword !== repeatPassword) {
+ return handleErrors('repeat', 'Password does not match.');
+ }
+
+ const isInvalid = validators.auth.password(newPassword);
+
+ if (isInvalid) {
+ return handleErrors('new', isInvalid);
+ }
+
+ const query = await createPostRequest('/api/user/update/password', {
+ currentPassword,
+ newPassword,
+ });
+
+ if (query.status === 200) {
+ toast.success('Password changed successfully!');
+ closeModal();
+ }
+ } catch (error) {
+ if (error.response?.data?.current) {
+ return handleErrors('current', error.response?.data?.current);
+ }
+
+ if (error?.response?.status === 400) {
+ return handleErrors('new', error.response.data?.message);
+ }
+
+ toast.error('Something went wrong, please try again later.');
+ }
+};
+
+export default handleChangePassword;
diff --git a/app/handlers/settings/account/transfer.js b/app/handlers/settings/account/transfer.js
new file mode 100644
index 0000000..9a25677
--- /dev/null
+++ b/app/handlers/settings/account/transfer.js
@@ -0,0 +1,20 @@
+import { toast } from 'sonner';
+
+import { createPostRequest } from '../../../services/axios';
+
+const handleTransferAccount = async (email, closeModal) => {
+ try {
+ const query = await createPostRequest('/api/user/transfer/ownership', {
+ email,
+ });
+
+ if (query.status === 200) {
+ toast.success('Ownership successfully transferred!');
+ closeModal();
+ }
+ } catch (error) {
+ toast.error('Something went wrong, please try again later.');
+ }
+};
+
+export default handleTransferAccount;
diff --git a/app/handlers/settings/account/username.js b/app/handlers/settings/account/username.js
new file mode 100644
index 0000000..e90a67c
--- /dev/null
+++ b/app/handlers/settings/account/username.js
@@ -0,0 +1,44 @@
+import { toast } from 'sonner';
+
+import * as validators from '../../../utils/validators';
+import { createPostRequest } from '../../../services/axios';
+
+const handleChangeUsername = async (
+ displayName,
+ setError,
+ closeModal,
+ handleErrors
+) => {
+ try {
+ const isInvalid = validators.auth.username(displayName);
+
+ if (isInvalid) {
+ return setError(isInvalid);
+ }
+
+ const query = await createPostRequest('/api/user/update/username', {
+ displayName,
+ });
+
+ if (query.status === 200) {
+ toast.success('Username changed successfully!');
+ closeModal();
+ }
+
+ return true;
+ } catch (error) {
+ if (error.response?.data?.current) {
+ return handleErrors('current', error.response?.data?.current);
+ }
+
+ if (error?.response?.status === 400) {
+ return handleErrors('new', error.response.data?.message);
+ }
+
+ toast.error('Something went wrong, please try again later.');
+
+ return false;
+ }
+};
+
+export default handleChangeUsername;
diff --git a/app/hooks/useGoBack.jsx b/app/hooks/useGoBack.jsx
new file mode 100644
index 0000000..31e0d13
--- /dev/null
+++ b/app/hooks/useGoBack.jsx
@@ -0,0 +1,19 @@
+import { useLocation, useNavigate } from 'react-router-dom';
+
+/**
+ * A function that returns a function to navigate back to the previous location or a fallback location.
+ *
+ * @param {Object} prev - an object containing the previous location and fallback location
+ * @return {void}
+ */
+const useGoBack = () => {
+ const { key: prevKey } = useLocation();
+ const navigate = useNavigate();
+
+ return ({ fallback = '/' } = {}) => {
+ const key = prevKey !== 'default' ? -1 : fallback;
+ navigate(key);
+ };
+};
+
+export default useGoBack;
diff --git a/app/hooks/useGraphStatus.jsx b/app/hooks/useGraphStatus.jsx
index 6eacf96..caa5a10 100644
--- a/app/hooks/useGraphStatus.jsx
+++ b/app/hooks/useGraphStatus.jsx
@@ -21,6 +21,7 @@ const useGraphStatus = (monitor = {}) => {
return setStatusHeartbeats(query.data);
}
} catch (error) {
+ console.log(error);
toast.error('Failed to fetch monitor heartbeats');
}
};
diff --git a/app/hooks/useRegister.jsx b/app/hooks/useRegister.jsx
index beba6f3..7414302 100644
--- a/app/hooks/useRegister.jsx
+++ b/app/hooks/useRegister.jsx
@@ -47,6 +47,7 @@ const useRegister = () => {
}));
}
} catch (error) {
+ console.log(error);
return toast.error('Error occurred while checking if email exists.');
}
}
diff --git a/app/layout/global.jsx b/app/layout/global.jsx
index ea4a4b3..3369927 100644
--- a/app/layout/global.jsx
+++ b/app/layout/global.jsx
@@ -36,6 +36,7 @@ const GlobalLayout = ({ children }) => {
setMonitors(data);
setTimeouts(data, fetchMonitorById);
} catch (error) {
+ console.log(error);
if (error.response?.status === 401) {
return navigate('/login');
}
diff --git a/app/pages/settings.jsx b/app/pages/settings.jsx
index 9f57c6a..8deff02 100644
--- a/app/pages/settings.jsx
+++ b/app/pages/settings.jsx
@@ -1,21 +1,48 @@
import './settings.scss';
-import { useState } from 'react';
-import SettingsTab from '../components/settings/tab';
-import SettingsGeneral from '../components/settings/general';
-import SettingsAbout from '../components/settings/about';
-import ManageTeam from '../components/settings/manage';
+
+// import dependencies
+import { useEffect, useState } from 'react';
+
+// import local files
+import useGoBack from '../hooks/useGoBack';
+// import SettingsMobile from '../components/settings/ui/menu/mobile';
+import SetttingsDesktop from '../components/settings/ui/menu/desktop';
+import SettingsMobile from '../components/settings/ui/menu/mobile';
const Settings = () => {
- const [tab, setTab] = useState('general');
- const handleTabUpdate = (tab) => setTab(tab);
+ const [tab, setTab] = useState('Account');
+ const handleTabUpdate = (tab) => {
+ return setTab(tab);
+ };
+ const goBack = useGoBack();
+
+ const handleKeydown = (event, isHandler = false) => {
+ if (event?.key === 'Escape' || event?.key === 'Esc' || isHandler) {
+ goBack();
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener('keydown', handleKeydown);
+
+ return () => {
+ document.removeEventListener('keydown', handleKeydown);
+ };
+ }, []);
return (
-
-
- {tab === 'general' && }
- {tab === 'about' && }
- {tab === 'manage' && }
-
+ <>
+
+
+
+
+
+
+ >
);
};
diff --git a/app/pages/settings.scss b/app/pages/settings.scss
index 53c7186..f667a70 100644
--- a/app/pages/settings.scss
+++ b/app/pages/settings.scss
@@ -8,18 +8,61 @@
border-radius: pxToRem(24);
overflow: hidden;
box-shadow: var(--shadow-sm);
+ max-width: pxToRem(1400);
+ margin: pxToRem(16);
+ border: 1px solid var(--accent-700);
+ position: relative;
+}
+
+.settings-container {
+ display: flex;
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+ justify-content: center;
+}
+
+.settings-close {
+ position: absolute;
+ top: pxToRem(12);
+ right: pxToRem(16);
+ border: 2px solid var(--accent-500);
+ display: flex;
+ padding: pxToRem(8);
+ border-radius: var(--radius-pill);
+ color: var(--accent-200);
+
+ &:hover {
+ cursor: pointer;
+ color: var(--accent-100);
+ border-color: var(--accent-200);
+ }
+}
+
+.settings-back-button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius-pill);
+ color: var(--accent-200);
+ padding: pxToRem(8);
+
+ &:hover {
+ cursor: pointer;
+ color: var(--accent-100);
+ border-color: var(--accent-200);
+ }
}
-@include tablet {
+@include mobile {
.settings-content {
- flex-direction: column;
- width: 100%;
- overflow: auto;
+ display: block;
+ margin: 0;
+ border-radius: 0;
}
- .settings-tab {
- display: flex;
- flex-direction: column;
- width: 100% !important;
+ .settings-close {
+ top: pxToRem(8);
+ right: pxToRem(8);
}
}
diff --git a/app/styles/global.scss b/app/styles/global.scss
index ede499e..5da269d 100644
--- a/app/styles/global.scss
+++ b/app/styles/global.scss
@@ -6,3 +6,57 @@
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
}
+
+.mobile-hidden {
+ @include mobile {
+ display: none;
+ }
+}
+
+.tablet-hidden {
+ @include tablet {
+ display: none;
+ }
+}
+
+.laptop-hidden {
+ @include laptop {
+ display: none;
+ }
+}
+
+.desktop-hidden {
+ @include desktop {
+ display: none;
+ }
+}
+
+.mobile-shown {
+ display: none;
+
+ @include mobile {
+ display: flex;
+ }
+}
+
+.tablet-shown {
+ display: none;
+
+ @include tablet {
+ display: flex;
+ }
+}
+
+.laptop-shown {
+ @include laptop {
+ display: none;
+ }
+}
+
+.desktop-shown {
+ display: none;
+
+ @include desktop {
+ display: flex;
+ }
+}
diff --git a/app/styles/themes.scss b/app/styles/themes.scss
index 7d42832..80c8e2b 100644
--- a/app/styles/themes.scss
+++ b/app/styles/themes.scss
@@ -89,6 +89,55 @@
--primary-900: var(--pink-900);
}
+@media (prefers-color-scheme: dark) {
+ :root {
+ --font-color: #f3f6fb;
+
+ --accent-50: var(--gray-50);
+ --accent-100: var(--gray-100);
+ --accent-200: var(--gray-200);
+ --accent-300: var(--gray-300);
+ --accent-400: var(--gray-400);
+ --accent-500: var(--gray-500);
+ --accent-600: var(--gray-600);
+ --accent-700: var(--gray-700);
+ --accent-800: var(--gray-800);
+ --accent-900: var(--gray-900);
+ }
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ --font-color: #0c0c0c;
+
+ --accent-50: var(--white-900);
+ --accent-100: var(--white-800);
+ --accent-200: var(--white-700);
+ --accent-300: var(--white-600);
+ --accent-400: var(--white-500);
+ --accent-500: var(--white-400);
+ --accent-600: var(--white-300);
+ --accent-700: var(--white-200);
+ --accent-800: var(--white-100);
+ --accent-900: var(--white-50);
+ }
+}
+
+[data-theme='dark'] {
+ --font-color: #f3f6fb;
+
+ --accent-50: var(--gray-50);
+ --accent-100: var(--gray-100);
+ --accent-200: var(--gray-200);
+ --accent-300: var(--gray-300);
+ --accent-400: var(--gray-400);
+ --accent-500: var(--gray-500);
+ --accent-600: var(--gray-600);
+ --accent-700: var(--gray-700);
+ --accent-800: var(--gray-800);
+ --accent-900: var(--gray-900);
+}
+
[data-theme='light'] {
--font-color: #0c0c0c;
diff --git a/app/utils/validators/user.js b/app/utils/validators/user.js
new file mode 100644
index 0000000..fcf8881
--- /dev/null
+++ b/app/utils/validators/user.js
@@ -0,0 +1,41 @@
+const defaultAvatars = [
+ 'Ape',
+ 'Bear',
+ 'Cat',
+ 'Dog',
+ 'Doggo',
+ 'Duck',
+ 'Eagle',
+ 'Fox',
+ 'Gerbil',
+ 'Hamster',
+ 'Hedgehog',
+ 'Koala',
+ 'Ostrich',
+ 'Panda',
+ 'Rabbit',
+ 'Rocket',
+ 'Tiger',
+];
+
+const isImageUrl = (url) => {
+ if (typeof url !== 'string') {
+ return false;
+ }
+
+ return url.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif)$/gim);
+};
+
+const isAvatar = (avatar) => {
+ if (avatar === null) {
+ return false;
+ }
+
+ if (!defaultAvatars.includes(avatar) && !isImageUrl(avatar)) {
+ return 'Avatar must be a valid Imgur URL or one of the default avatars.';
+ }
+
+ return false;
+};
+
+export { isAvatar };
diff --git a/package-lock.json b/package-lock.json
index e0d7fbf..1c578ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "lunalytics",
- "version": "0.4.7",
+ "version": "0.5.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lunalytics",
- "version": "0.4.7",
+ "version": "0.5.0",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"axios": "^1.6.2",
@@ -1260,6 +1260,14 @@
"node": ">= 8"
}
},
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.25",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
+ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
"node_modules/@remix-run/router": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.1.tgz",
@@ -1694,6 +1702,29 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/@vitest/ui": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-1.6.0.tgz",
+ "integrity": "sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@vitest/utils": "1.6.0",
+ "fast-glob": "^3.3.2",
+ "fflate": "^0.8.1",
+ "flatted": "^3.2.9",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "sirv": "^2.0.4"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "1.6.0"
+ }
+ },
"node_modules/@vitest/utils": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz",
@@ -4707,6 +4738,38 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -4741,6 +4804,14 @@
"pend": "~1.2.0"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -7272,6 +7343,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -7497,6 +7579,17 @@
"node": "*"
}
},
+ "node_modules/mrmime": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
+ "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -9309,6 +9402,22 @@
"node": ">=10"
}
},
+ "node_modules/sirv": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
+ "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/slice-ansi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
@@ -9894,6 +10003,17 @@
"node": ">=0.6"
}
},
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
diff --git a/package.json b/package.json
index bd1e250..d4cb606 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lunalytics",
- "version": "0.4.7",
+ "version": "0.5.0",
"description": "Open source Node.js server/website monitoring tool",
"private": true,
"author": "KSJaay
",
diff --git a/scripts/reset.js b/scripts/reset.js
index 3111d10..5987cc7 100644
--- a/scripts/reset.js
+++ b/scripts/reset.js
@@ -9,7 +9,7 @@ import SQLite from '../server/database/sqlite/setup.js';
import { generateHash } from '../server/utils/hashPassword.js';
const questions = [
- { type: 'input', name: 'email', message: 'Enter email added: ' },
+ { type: 'input', name: 'email', message: 'Enter email added:' },
];
const generatePassword = () => {
@@ -45,7 +45,7 @@ inquirer
const email = answers.email.toLowerCase().trim();
const client = await SQLite.connect();
- const emailExists = client('user').where({ email }).first();
+ const emailExists = await client('user').where({ email }).first();
if (!emailExists) {
logger.log(
@@ -54,7 +54,8 @@ inquirer
'ERROR',
false
);
- return;
+
+ process.exit(0);
}
const newPassword = generatePassword();
@@ -70,10 +71,9 @@ inquirer
);
await client.destroy();
- process.exit(1);
+ process.exit(0);
})
.catch((error) => {
- logger.log('', error, 'ERROR', false);
logger.log(
'RESET PASSWORD',
'Error resetting password, please try again.',
@@ -81,5 +81,7 @@ inquirer
false
);
- process.exit(1);
+ logger.log('', error, 'ERROR', false);
+
+ process.exit(0);
});
diff --git a/server/cache/certificates.js b/server/cache/certificates.js
index c7af210..149c8fc 100644
--- a/server/cache/certificates.js
+++ b/server/cache/certificates.js
@@ -28,7 +28,10 @@ class Certificates {
delete certificate.lastCheck;
delete certificate.nextCheck;
- await updateCertificate(monitorId, certificate);
+ await updateCertificate(monitorId, {
+ ...certificate,
+ issuer: JSON.stringify(certificate.issuer),
+ });
certificate.lastCheck = Date.now();
certificate.nextCheck = certificate.lastCheck + 86400000;
diff --git a/server/cache/index.js b/server/cache/index.js
index 94ccc55..53bc632 100644
--- a/server/cache/index.js
+++ b/server/cache/index.js
@@ -2,7 +2,7 @@
import Certificates from './certificates.js';
import Heartbeats from './heartbeats.js';
import Monitor from './monitors.js';
-import getCertInfo from '../tools/checkCertificate.js';
+import getCertInfo, { parseCert } from '../tools/checkCertificate.js';
import httpStatusCheck from '../tools/httpStatus.js';
import tcpStatusCheck from '../tools/tcpPing.js';
@@ -77,10 +77,13 @@ class Master {
const cert = await getCertInfo(monitor.url);
if (cert) {
- await this.certificates.update(monitorId, {
- ...certificate,
- ...cert,
- });
+ await this.certificates.update(
+ monitorId,
+ parseCert({
+ ...certificate,
+ ...cert,
+ })
+ );
}
}
}
diff --git a/server/class/monitor.js b/server/class/monitor.js
index bdaf576..3044b79 100644
--- a/server/class/monitor.js
+++ b/server/class/monitor.js
@@ -4,7 +4,7 @@ const parseJson = (str) => {
try {
return JSON.parse(str);
} catch (e) {
- return '';
+ return ['200-299'];
}
};
diff --git a/server/database/queries/user.js b/server/database/queries/user.js
index bee4023..a65f368 100644
--- a/server/database/queries/user.js
+++ b/server/database/queries/user.js
@@ -97,6 +97,14 @@ const updateUserPermission = (email, permission) => {
return SQLite.client('user').where({ email }).update({ permission });
};
+const updateUserPassword = (email, password) => {
+ const hashedPassword = generateHash(password);
+
+ return SQLite.client('user')
+ .where({ email })
+ .update({ password: hashedPassword });
+};
+
const getDemoUser = async () => {
const demoUser = await SQLite.client('user').where({ email: 'demo' }).first();
@@ -114,6 +122,13 @@ const getDemoUser = async () => {
return signCookie({ email: 'demo' });
};
+const transferOwnership = async (email, newOwner) => {
+ await SQLite.client('user').where({ email }).update({ permission: 4 });
+ return SQLite.client('user')
+ .where({ email: newOwner })
+ .update({ permission: 1 });
+};
+
export {
signInUser,
registerUser,
@@ -125,5 +140,7 @@ export {
declineAccess,
approveAccess,
updateUserPermission,
+ updateUserPassword,
getDemoUser,
+ transferOwnership,
};
diff --git a/server/middleware/monitor/id.js b/server/middleware/monitor/id.js
index d7312b6..93368e5 100644
--- a/server/middleware/monitor/id.js
+++ b/server/middleware/monitor/id.js
@@ -11,6 +11,11 @@ const fetchMonitorUsingId = async (request, response) => {
}
const data = await cache.monitors.get(monitorId);
+
+ if (!data) {
+ return response.status(404).json({ error: 'Monitor not found' });
+ }
+
const heartbeats = await cache.heartbeats.get(data.monitorId);
const cert = await cache.certificates.get(data.monitorId);
diff --git a/server/middleware/user/deleteAccount.js b/server/middleware/user/deleteAccount.js
new file mode 100644
index 0000000..975c0cb
--- /dev/null
+++ b/server/middleware/user/deleteAccount.js
@@ -0,0 +1,32 @@
+import { userExists, declineAccess } from '../../database/queries/user.js';
+import { handleError } from '../../utils/errors.js';
+
+const deleteAccountMiddleware = async (request, response) => {
+ try {
+ const { access_token } = request.cookies;
+
+ if (!access_token) {
+ return response.sendStatus(401);
+ }
+
+ const user = await userExists(access_token);
+
+ if (!user) {
+ return response.sendStatus(401);
+ }
+
+ if (user.permission === 1) {
+ return response
+ .status(403)
+ .send('Please transfer ownership before deleting your account.');
+ }
+
+ await declineAccess(user.email);
+
+ return response.sendStatus(200);
+ } catch (error) {
+ handleError(error, response);
+ }
+};
+
+export default deleteAccountMiddleware;
diff --git a/server/middleware/user/hasAdmin.js b/server/middleware/user/hasAdmin.js
index a47dc4e..7213bf6 100644
--- a/server/middleware/user/hasAdmin.js
+++ b/server/middleware/user/hasAdmin.js
@@ -1,23 +1,28 @@
import { userExists } from '../../database/queries/user.js';
+import { handleError } from '../../utils/errors.js';
const hasAdminPermissions = async (request, response, next) => {
- const { access_token } = request.cookies;
+ try {
+ const { access_token } = request.cookies;
- if (!access_token) {
- return response.sendStatus(401);
- }
+ if (!access_token) {
+ return response.sendStatus(401);
+ }
- const user = await userExists(access_token);
+ const user = await userExists(access_token);
- if (!user) {
- return response.sendStatus(401);
- }
+ if (!user) {
+ return response.sendStatus(401);
+ }
- if (user.permission !== 1 && user.permission !== 2) {
- return response.sendStatus(401);
- }
+ if (user.permission !== 1 && user.permission !== 2) {
+ return response.sendStatus(401);
+ }
- return next();
+ return next();
+ } catch (error) {
+ handleError(error, response);
+ }
};
export default hasAdminPermissions;
diff --git a/server/middleware/user/hasEditor.js b/server/middleware/user/hasEditor.js
index 009087f..78c4d87 100644
--- a/server/middleware/user/hasEditor.js
+++ b/server/middleware/user/hasEditor.js
@@ -1,23 +1,32 @@
import { userExists } from '../../database/queries/user.js';
+import { handleError } from '../../utils/errors.js';
const hasEditorPermissions = async (request, response, next) => {
- const { access_token } = request.cookies;
+ try {
+ const { access_token } = request.cookies;
- if (!access_token) {
- return response.sendStatus(401);
- }
+ if (!access_token) {
+ return response.sendStatus(401);
+ }
- const user = await userExists(access_token);
+ const user = await userExists(access_token);
- if (!user) {
- return response.sendStatus(401);
- }
+ if (!user) {
+ return response.sendStatus(401);
+ }
- if (user.permission !== 1 && user.permission !== 2 && user.permission !== 3) {
- return response.sendStatus(401);
- }
+ if (
+ user.permission !== 1 &&
+ user.permission !== 2 &&
+ user.permission !== 3
+ ) {
+ return response.sendStatus(401);
+ }
- return next();
+ return next();
+ } catch (error) {
+ handleError(error, response);
+ }
};
export default hasEditorPermissions;
diff --git a/server/middleware/user/transferOwnership.js b/server/middleware/user/transferOwnership.js
new file mode 100644
index 0000000..afabd1d
--- /dev/null
+++ b/server/middleware/user/transferOwnership.js
@@ -0,0 +1,47 @@
+import {
+ emailExists,
+ transferOwnership,
+ userExists,
+} from '../../database/queries/user.js';
+import { handleError } from '../../utils/errors.js';
+
+const transferOwnershipMiddleware = async (request, response) => {
+ try {
+ const {
+ cookies: { access_token },
+ body: { email },
+ } = request;
+
+ if (!access_token) {
+ return response.sendStatus(401);
+ }
+
+ if (!email) {
+ return response.sendStatus(400);
+ }
+
+ const user = await userExists(access_token);
+
+ if (!user) {
+ return response.sendStatus(401);
+ }
+
+ if (user.permission !== 1) {
+ return response.sendStatus(401);
+ }
+
+ const newOwnerExists = await emailExists(email);
+
+ if (!newOwnerExists) {
+ return response.sendStatus(400);
+ }
+
+ await transferOwnership(user.email, email);
+
+ return response.sendStatus(200);
+ } catch (error) {
+ handleError(error, response);
+ }
+};
+
+export default transferOwnershipMiddleware;
diff --git a/server/middleware/user/update/avatar.js b/server/middleware/user/update/avatar.js
index 73e05a7..8eb8fa5 100644
--- a/server/middleware/user/update/avatar.js
+++ b/server/middleware/user/update/avatar.js
@@ -21,7 +21,7 @@ const userUpdateAvatar = async (request, response) => {
const { avatar } = request.body;
- if (!avatar || user.avatar === avatar) {
+ if (avatar !== null && (!avatar || user.avatar === avatar)) {
return response.sendStatus(200);
}
diff --git a/server/middleware/user/update/password.js b/server/middleware/user/update/password.js
new file mode 100644
index 0000000..3dc79b1
--- /dev/null
+++ b/server/middleware/user/update/password.js
@@ -0,0 +1,47 @@
+import {
+ updateUserPassword,
+ userExists,
+} from '../../../database/queries/user.js';
+import { handleError } from '../../../utils/errors.js';
+import { verifyPassword } from '../../../utils/hashPassword.js';
+import validators from '../../../utils/validators/index.js';
+
+const userUpdatePassword = async (request, response) => {
+ try {
+ const { access_token } = request.cookies;
+
+ if (!access_token) {
+ return response.sendStatus(401);
+ }
+
+ const user = await userExists(access_token);
+
+ if (!user) {
+ return response.sendStatus(401);
+ }
+
+ const { currentPassword, newPassword } = request.body;
+
+ const passwordMatches = verifyPassword(currentPassword, user.password);
+
+ if (!passwordMatches) {
+ return response.status(401).json({
+ current: 'Password does not match your current password',
+ });
+ }
+
+ const isInvalidPassword = validators.auth.password(newPassword);
+
+ if (isInvalidPassword) {
+ return response.status(400).send(isInvalidPassword);
+ }
+
+ await updateUserPassword(user.email, newPassword);
+
+ return response.sendStatus(200);
+ } catch (error) {
+ handleError(error, response);
+ }
+};
+
+export default userUpdatePassword;
diff --git a/server/routes/user.js b/server/routes/user.js
index 765ef58..8c8748e 100644
--- a/server/routes/user.js
+++ b/server/routes/user.js
@@ -12,6 +12,9 @@ import userUpdateAvatar from '../middleware/user/update/avatar.js';
import userUpdateUsername from '../middleware/user/update/username.js';
import { userExists, emailExists } from '../database/queries/user.js';
import { cleanMonitor } from '../class/monitor.js';
+import userUpdatePassword from '../middleware/user/update/password.js';
+import transferOwnershipMiddleware from '../middleware/user/transferOwnership.js';
+import deleteAccountMiddleware from '../middleware/user/deleteAccount.js';
router.get('/', async (request, response) => {
const { access_token } = request.cookies;
@@ -48,9 +51,14 @@ router.get('/monitors', async (request, response) => {
for (const monitor of monitors) {
const heartbeats = await cache.heartbeats.get(monitor.monitorId);
- const cert = await cache.certificates.get(monitor.monitorId);
monitor.heartbeats = heartbeats;
- monitor.cert = cert;
+
+ monitor.cert = { isValid: false };
+
+ if (monitor.type === 'http') {
+ const cert = await cache.certificates.get(monitor.monitorId);
+ monitor.cert = cert;
+ }
query.push(cleanMonitor(monitor));
}
@@ -60,6 +68,8 @@ router.get('/monitors', async (request, response) => {
router.post('/update/username', userUpdateUsername);
+router.post('/update/password', userUpdatePassword);
+
router.post('/update/avatar', userUpdateAvatar);
router.get('/team', teamMembersListMiddleware);
@@ -72,4 +82,8 @@ router.post('/access/remove', hasAdminPermissions, accessRemoveMiddleware);
router.post('/permission/update', permissionUpdateMiddleware);
+router.post('/transfer/ownership', transferOwnershipMiddleware);
+
+router.post('/delete/account', deleteAccountMiddleware);
+
export default router;
diff --git a/server/tools/checkCertificate.js b/server/tools/checkCertificate.js
index b6861f9..02efb79 100644
--- a/server/tools/checkCertificate.js
+++ b/server/tools/checkCertificate.js
@@ -51,7 +51,7 @@ const getDaysRemaining = (validFrom, validTo) => {
return daysRemaining;
};
-const parseCert = (cert) => {
+export const parseCert = (cert) => {
const validOn = cert.subjectaltname
?.replace(/DNS:|IP Address:/g, '')
.split(', ');
diff --git a/server/utils/validators/user.js b/server/utils/validators/user.js
index ab5061f..fcf8881 100644
--- a/server/utils/validators/user.js
+++ b/server/utils/validators/user.js
@@ -18,8 +18,20 @@ const defaultAvatars = [
'Tiger',
];
+const isImageUrl = (url) => {
+ if (typeof url !== 'string') {
+ return false;
+ }
+
+ return url.match(/^https?:\/\/.+\.(jpg|jpeg|png|gif)$/gim);
+};
+
const isAvatar = (avatar) => {
- if (!defaultAvatars.includes(avatar)) {
+ if (avatar === null) {
+ return false;
+ }
+
+ if (!defaultAvatars.includes(avatar) && !isImageUrl(avatar)) {
return 'Avatar must be a valid Imgur URL or one of the default avatars.';
}
diff --git a/test/e2e/verify.test.js b/test/e2e/verify.test.js
index d6f25a4..43f9c49 100644
--- a/test/e2e/verify.test.js
+++ b/test/e2e/verify.test.js
@@ -25,7 +25,7 @@ describe('Verify User', () => {
);
cy.visit('/settings');
- cy.contains('[id="manage"]', 'Manage Team').click();
+ cy.contains('[id="Manage-Team"]', 'Manage Team').click();
cy.get(`[id="accept-${username}"]`).click();
@@ -48,7 +48,7 @@ describe('Verify User', () => {
cy.visit('/settings');
- cy.get('[id="manage"]').click();
+ cy.get('[id="Manage-Team"]').click();
cy.get(`[id="decline-${secondUsername}"]`).click();
cy.get('[id="manage-decline-button"]').click();
diff --git a/test/server/middleware/monitor/id.test.js b/test/server/middleware/monitor/id.test.js
index bc8a209..904b0ff 100644
--- a/test/server/middleware/monitor/id.test.js
+++ b/test/server/middleware/monitor/id.test.js
@@ -47,6 +47,14 @@ describe('Fetch Monitor Using Id - Middleware', () => {
expect(cache.monitors.get).toHaveBeenCalledWith(monitorId);
});
+ it('should return 404 when monitor is not found', async () => {
+ cache.monitors.get.mockReturnValue(null);
+
+ await fetchMonitorUsingId(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(404);
+ });
+
it('should call cache.heartbeats.get with monitorId', async () => {
await fetchMonitorUsingId(fakeRequest, fakeResponse);
diff --git a/test/server/middleware/user/deleteAccount.test.js b/test/server/middleware/user/deleteAccount.test.js
new file mode 100644
index 0000000..b7cb757
--- /dev/null
+++ b/test/server/middleware/user/deleteAccount.test.js
@@ -0,0 +1,82 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { createRequest, createResponse } from 'node-mocks-http';
+import {
+ userExists,
+ declineAccess,
+} from '../../../../server/database/queries/user';
+import deleteAccountMiddleware from '../../../../server/middleware/user/deleteAccount';
+
+vi.mock('../../../../server/database/queries/user');
+
+describe('deleteAccountMiddleware - Middleware', () => {
+ const access_token = 'test_token';
+
+ let fakeRequest;
+ let fakeResponse;
+ let fakeNext;
+
+ beforeEach(() => {
+ userExists = vi
+ .fn()
+ .mockReturnValue({ email: 'KSJaay@lunalytics.xyz', permission: 2 });
+ declineAccess = vi.fn();
+
+ fakeRequest = createRequest();
+ fakeResponse = createResponse();
+ fakeNext = vi.fn();
+
+ fakeRequest.cookies = { access_token };
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('should return 401 when access_token is not provided', async () => {
+ fakeRequest.cookies = {};
+
+ await deleteAccountMiddleware(fakeRequest, fakeResponse, fakeNext);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it('should call userExists with access_token', async () => {
+ await deleteAccountMiddleware(fakeRequest, fakeResponse, fakeNext);
+
+ expect(userExists).toHaveBeenCalledWith(access_token);
+ });
+
+ it('should return 401 when user does not exist', async () => {
+ userExists = vi.fn().mockReturnValue(null);
+
+ await deleteAccountMiddleware(fakeRequest, fakeResponse, fakeNext);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it('should return 403 when user does has ownership', async () => {
+ userExists = vi
+ .fn()
+ .mockReturnValue({ email: 'KSJaay@lunalytics.xyz', permission: 1 });
+
+ await deleteAccountMiddleware(fakeRequest, fakeResponse, fakeNext);
+
+ expect(fakeResponse.statusCode).toEqual(403);
+
+ expect(fakeResponse._getData()).toEqual(
+ 'Please transfer ownership before deleting your account.'
+ );
+ });
+
+ it('should call declineAccess with user email', async () => {
+ await deleteAccountMiddleware(fakeRequest, fakeResponse, fakeNext);
+
+ expect(declineAccess).toHaveBeenCalledWith('KSJaay@lunalytics.xyz');
+ });
+
+ it('should return 200', async () => {
+ await deleteAccountMiddleware(fakeRequest, fakeResponse, fakeNext);
+
+ expect(fakeResponse.statusCode).toEqual(200);
+ });
+});
diff --git a/test/server/middleware/user/permission/update.test.js b/test/server/middleware/user/permission/update.test.js
index 6adedf3..7420872 100644
--- a/test/server/middleware/user/permission/update.test.js
+++ b/test/server/middleware/user/permission/update.test.js
@@ -76,8 +76,6 @@ describe('permissionUpdateMiddleware - Middleware', () => {
await permissionUpdateMiddleware(fakeRequest, fakeResponse);
- console.log(fakeResponse);
-
expect(fakeResponse.statusCode).toEqual(400);
expect(spy).toHaveBeenCalledWith('You cannot change this user permission.');
});
diff --git a/test/server/middleware/user/transferOwnership.test.js b/test/server/middleware/user/transferOwnership.test.js
new file mode 100644
index 0000000..d309010
--- /dev/null
+++ b/test/server/middleware/user/transferOwnership.test.js
@@ -0,0 +1,106 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { createRequest, createResponse } from 'node-mocks-http';
+import {
+ userExists,
+ emailExists,
+ transferOwnership,
+} from '../../../../server/database/queries/user';
+import transferOwnershipMiddleware from '../../../../server/middleware/user/transferOwnership';
+
+vi.mock('../../../../server/database/queries/user');
+
+describe('transferOwnershipMiddleware - Middleware', () => {
+ const access_token = 'test_token';
+
+ let fakeRequest;
+ let fakeResponse;
+
+ beforeEach(() => {
+ userExists = vi
+ .fn()
+ .mockReturnValue({ email: 'KSJaay@lunalytics.xyz', permission: 1 });
+ emailExists = vi.fn().mockReturnValue(true);
+ transferOwnership = vi.fn();
+
+ fakeRequest = createRequest();
+ fakeResponse = createResponse();
+
+ fakeRequest.cookies = { access_token };
+ fakeRequest.body = { email: '123@lunalytics.xyz' };
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('should return 401 when access_token is not provided', async () => {
+ fakeRequest.cookies = {};
+
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it('should return 400 when email is not provided', async () => {
+ fakeRequest.body = {};
+
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(400);
+ });
+
+ it('should call userExists with access_token', async () => {
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(userExists).toHaveBeenCalledWith(access_token);
+ });
+
+ it('should return 401 when user does not exist', async () => {
+ userExists = vi.fn().mockReturnValue(null);
+
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it('should return 401 when user does has ownership', async () => {
+ userExists = vi
+ .fn()
+ .mockReturnValue({ email: 'KSJaay@lunalytics.xyz', permission: 2 });
+
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it('should call emailExists with body email', async () => {
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(emailExists).toHaveBeenCalledWith('123@lunalytics.xyz');
+ });
+
+ it('should return 400 when email does not exist', async () => {
+ emailExists = vi.fn().mockReturnValue(false);
+
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(400);
+
+ expect(transferOwnership).not.toHaveBeenCalled();
+ });
+
+ it('should call transferOwnership with user.email and body.email', async () => {
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(transferOwnership).toHaveBeenCalledWith(
+ 'KSJaay@lunalytics.xyz',
+ '123@lunalytics.xyz'
+ );
+ });
+
+ it('should return 200', async () => {
+ await transferOwnershipMiddleware(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(200);
+ });
+});
diff --git a/test/server/middleware/user/update/password.test.js b/test/server/middleware/user/update/password.test.js
new file mode 100644
index 0000000..85ca3c7
--- /dev/null
+++ b/test/server/middleware/user/update/password.test.js
@@ -0,0 +1,88 @@
+import { createRequest, createResponse } from 'node-mocks-http';
+import {
+ updateUserPassword,
+ userExists,
+} from '../../../../../server/database/queries/user';
+import userUpdatePassword from '../../../../../server/middleware/user/update/password';
+import { verifyPassword } from '../../../../../server/utils/hashPassword';
+
+vi.mock('../../../../../server/database/queries/user');
+vi.mock('../../../../../server/utils/hashPassword');
+
+describe('userUpdatePassword - Middleware', () => {
+ const user = {
+ email: 'KSJaay@lunalytics.xyz',
+ displayName: 'KSJaay',
+ avatar: 'Panda',
+ isVerified: true,
+ };
+
+ let fakeRequest;
+ let fakeResponse;
+
+ beforeEach(() => {
+ fakeRequest = createRequest();
+ fakeResponse = createResponse();
+
+ userExists = vi.fn().mockReturnValue(user);
+ verifyPassword = vi.fn().mockReturnValue(true);
+ updateUserPassword = vi.fn();
+
+ fakeRequest.cookies = { access_token: 'test_token' };
+
+ fakeRequest.body = {
+ currentPassword: 'testUser123',
+ newPassword: 'testUser1234',
+ };
+ });
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it('should return 401 when access_token is missing', async () => {
+ fakeRequest.cookies = {};
+
+ await userUpdatePassword(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it('should return 401 when user does not exist', async () => {
+ userExists = vi.fn().mockReturnValue(null);
+
+ await userUpdatePassword(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ });
+
+ it("should return 401 when currentPassword and user.password aren't the same", async () => {
+ verifyPassword = vi.fn().mockReturnValue(false);
+ await userUpdatePassword(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(401);
+ expect(fakeResponse._getJSONData()).toEqual({
+ current: 'Password does not match your current password',
+ });
+ });
+
+ it('should return 400 when password is invalid', async () => {
+ fakeRequest.body.newPassword = 'test';
+
+ await userUpdatePassword(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(400);
+ });
+
+ it('should call updateUserPassword with email and newPassword', async () => {
+ await userUpdatePassword(fakeRequest, fakeResponse);
+
+ expect(updateUserPassword).toHaveBeenCalledWith(user.email, 'testUser1234');
+ });
+
+ it('should return 200 when password is updated', async () => {
+ await userUpdatePassword(fakeRequest, fakeResponse);
+
+ expect(fakeResponse.statusCode).toEqual(200);
+ });
+});