diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aa16d2a6..2f17113e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8608,6 +8608,19 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "requires": { + "moment": ">= 2.9.0" + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 61b59421..7ba15ffe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,8 @@ "@testing-library/user-event": "^7.2.1", "bootstrap": "^4.5.0", "firebase": "^7.15.5", - "history": "^5.0.0", + "moment-timezone": "^0.5.31", + "history": "^5.0.0" "react": "^16.13.1", "react-bootstrap": "1.0.1", "react-dom": "^16.13.1", diff --git a/frontend/src/components/Utils/time.js b/frontend/src/components/Utils/time.js index b633080d..ff6679dc 100644 --- a/frontend/src/components/Utils/time.js +++ b/frontend/src/components/Utils/time.js @@ -1,3 +1,5 @@ +import * as moment from 'moment-timezone'; +import { countryCodes } from '../../constants/countries.js'; import * as firebase from 'firebase/app'; /** @@ -6,7 +8,7 @@ import * as firebase from 'firebase/app'; * * @param {int} msTimestamp Timestamp in milliseconds of desired date. * @param {string} timezone Timezone in which to convert. - * @returns {string} Time formatted into desired pretty string. + * @return {string} Time formatted into desired pretty string. */ export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_York') { const date = new Date(msTimestamp); @@ -24,7 +26,7 @@ export function timestampToTimeFormatted(msTimestamp, timezone = 'America/New_Yo * * @param {int} msTimestamp Timestamp in milliseconds of desired date. * @param {string} timezone Timezone in which to convert. - * @returns {string} Time formatted into desired pretty string. + * @return {string} Time formatted into desired pretty string. */ export function timestampToDateFormatted(msTimestamp, timezone='America/New_York') { const date = new Date(msTimestamp); @@ -44,7 +46,7 @@ export function timestampToDateFormatted(msTimestamp, timezone='America/New_York * * @param {int} msTimestamp Timestamp in milliseconds of desired date. * @param {string} timezone Timezone in which to convert. - * @returns {string} Time formatted into desired pretty string. + * @return {string} Time formatted into desired pretty string. */ export function timestampToFormatted(msTimestamp, timezone = 'America/New_York') { let date = new Date(msTimestamp); @@ -85,3 +87,22 @@ export function getTimestampFromDateString(dateStr) { export function timestampToISOString(timestamp) { return timestamp.toDate().toISOString().substring(0,10); } + +/** + * Returns all the time zones in a country (in displayable format). + * + * @param {string} countryName The name of the country for which to get the time zones. + * @return {string[]} The list of time zones in the provided country. + */ +export function timezonesForCountry(countryName) { + let zones; + try { + const countryCode = countryCodes[countryName]; + zones = moment.tz.zonesForCountry(countryCode); + } catch (e) { + zones = moment.tz.names(); // List of all timezones. + } + return zones.map(e => { + return e.replace(/[_]/g, ' '); + }); +} \ No newline at end of file diff --git a/frontend/src/components/Utils/time.test.js b/frontend/src/components/Utils/time.test.js index b02a9102..2ad87fbd 100644 --- a/frontend/src/components/Utils/time.test.js +++ b/frontend/src/components/Utils/time.test.js @@ -6,48 +6,51 @@ import * as utils from './time.js'; const TZ_CHICAGO = 'America/Chicago'; const TZ_SINGAPORE = 'Asia/Singapore'; +const ALLTZS = ["Africa/Abidjan", "Africa/Accra", "Africa/Addis Ababa", "Africa/Algiers", "Africa/Asmara", "Africa/Asmera", "Africa/Bamako", "Africa/Bangui", "Africa/Banjul", "Africa/Bissau", "Africa/Blantyre", "Africa/Brazzaville", "Africa/Bujumbura", "Africa/Cairo", "Africa/Casablanca", "Africa/Ceuta", "Africa/Conakry", "Africa/Dakar", "Africa/Dar es Salaam", "Africa/Djibouti", "Africa/Douala", "Africa/El Aaiun", "Africa/Freetown", "Africa/Gaborone", "Africa/Harare", "Africa/Johannesburg", "Africa/Juba", "Africa/Kampala", "Africa/Khartoum", "Africa/Kigali", "Africa/Kinshasa", "Africa/Lagos", "Africa/Libreville", "Africa/Lome", "Africa/Luanda", "Africa/Lubumbashi", "Africa/Lusaka", "Africa/Malabo", "Africa/Maputo", "Africa/Maseru", "Africa/Mbabane", "Africa/Mogadishu", "Africa/Monrovia", "Africa/Nairobi", "Africa/Ndjamena", "Africa/Niamey", "Africa/Nouakchott", "Africa/Ouagadougou", "Africa/Porto-Novo", "Africa/Sao Tome", "Africa/Timbuktu", "Africa/Tripoli", "Africa/Tunis", "Africa/Windhoek", "America/Adak", "America/Anchorage", "America/Anguilla", "America/Antigua", "America/Araguaina", "America/Argentina/Buenos Aires", "America/Argentina/Catamarca", "America/Argentina/ComodRivadavia", "America/Argentina/Cordoba", "America/Argentina/Jujuy", "America/Argentina/La Rioja", "America/Argentina/Mendoza", "America/Argentina/Rio Gallegos", "America/Argentina/Salta", "America/Argentina/San Juan", "America/Argentina/San Luis", "America/Argentina/Tucuman", "America/Argentina/Ushuaia", "America/Aruba", "America/Asuncion", "America/Atikokan", "America/Atka", "America/Bahia", "America/Bahia Banderas", "America/Barbados", "America/Belem", "America/Belize", "America/Blanc-Sablon", "America/Boa Vista", "America/Bogota", "America/Boise", "America/Buenos Aires", "America/Cambridge Bay", "America/Campo Grande", "America/Cancun", "America/Caracas", "America/Catamarca", "America/Cayenne", "America/Cayman", "America/Chicago", "America/Chihuahua", "America/Coral Harbour", "America/Cordoba", "America/Costa Rica", "America/Creston", "America/Cuiaba", "America/Curacao", "America/Danmarkshavn", "America/Dawson", "America/Dawson Creek", "America/Denver", "America/Detroit", "America/Dominica", "America/Edmonton", "America/Eirunepe", "America/El Salvador", "America/Ensenada", "America/Fort Nelson", "America/Fort Wayne", "America/Fortaleza", "America/Glace Bay", "America/Godthab", "America/Goose Bay", "America/Grand Turk", "America/Grenada", "America/Guadeloupe", "America/Guatemala", "America/Guayaquil", "America/Guyana", "America/Halifax", "America/Havana", "America/Hermosillo", "America/Indiana/Indianapolis", "America/Indiana/Knox", "America/Indiana/Marengo", "America/Indiana/Petersburg", "America/Indiana/Tell City", "America/Indiana/Vevay", "America/Indiana/Vincennes", "America/Indiana/Winamac", "America/Indianapolis", "America/Inuvik", "America/Iqaluit", "America/Jamaica", "America/Jujuy", "America/Juneau", "America/Kentucky/Louisville", "America/Kentucky/Monticello", "America/Knox IN", "America/Kralendijk", "America/La Paz", "America/Lima", "America/Los Angeles", "America/Louisville", "America/Lower Princes", "America/Maceio", "America/Managua", "America/Manaus", "America/Marigot", "America/Martinique", "America/Matamoros", "America/Mazatlan", "America/Mendoza", "America/Menominee", "America/Merida", "America/Metlakatla", "America/Mexico City", "America/Miquelon", "America/Moncton", "America/Monterrey", "America/Montevideo", "America/Montreal", "America/Montserrat", "America/Nassau", "America/New York", "America/Nipigon", "America/Nome", "America/Noronha", "America/North Dakota/Beulah", "America/North Dakota/Center", "America/North Dakota/New Salem", "America/Nuuk", "America/Ojinaga", "America/Panama", "America/Pangnirtung", "America/Paramaribo", "America/Phoenix", "America/Port-au-Prince", "America/Port of Spain", "America/Porto Acre", "America/Porto Velho", "America/Puerto Rico", "America/Punta Arenas", "America/Rainy River", "America/Rankin Inlet", "America/Recife", "America/Regina", "America/Resolute", "America/Rio Branco", "America/Rosario", "America/Santa Isabel", "America/Santarem", "America/Santiago", "America/Santo Domingo", "America/Sao Paulo", "America/Scoresbysund", "America/Shiprock", "America/Sitka", "America/St Barthelemy", "America/St Johns", "America/St Kitts", "America/St Lucia", "America/St Thomas", "America/St Vincent", "America/Swift Current", "America/Tegucigalpa", "America/Thule", "America/Thunder Bay", "America/Tijuana", "America/Toronto", "America/Tortola", "America/Vancouver", "America/Virgin", "America/Whitehorse", "America/Winnipeg", "America/Yakutat", "America/Yellowknife", "Antarctica/Casey", "Antarctica/Davis", "Antarctica/DumontDUrville", "Antarctica/Macquarie", "Antarctica/Mawson", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", "Antarctica/South Pole", "Antarctica/Syowa", "Antarctica/Troll", "Antarctica/Vostok", "Arctic/Longyearbyen", "Asia/Aden", "Asia/Almaty", "Asia/Amman", "Asia/Anadyr", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Ashgabat", "Asia/Ashkhabad", "Asia/Atyrau", "Asia/Baghdad", "Asia/Bahrain", "Asia/Baku", "Asia/Bangkok", "Asia/Barnaul", "Asia/Beirut", "Asia/Bishkek", "Asia/Brunei", "Asia/Calcutta", "Asia/Chita", "Asia/Choibalsan", "Asia/Chongqing", "Asia/Chungking", "Asia/Colombo", "Asia/Dacca", "Asia/Damascus", "Asia/Dhaka", "Asia/Dili", "Asia/Dubai", "Asia/Dushanbe", "Asia/Famagusta", "Asia/Gaza", "Asia/Harbin", "Asia/Hebron", "Asia/Ho Chi Minh", "Asia/Hong Kong", "Asia/Hovd", "Asia/Irkutsk", "Asia/Istanbul", "Asia/Jakarta", "Asia/Jayapura", "Asia/Jerusalem", "Asia/Kabul", "Asia/Kamchatka", "Asia/Karachi", "Asia/Kashgar", "Asia/Kathmandu", "Asia/Katmandu", "Asia/Khandyga", "Asia/Kolkata", "Asia/Krasnoyarsk", "Asia/Kuala Lumpur", "Asia/Kuching", "Asia/Kuwait", "Asia/Macao", "Asia/Macau", "Asia/Magadan", "Asia/Makassar", "Asia/Manila", "Asia/Muscat", "Asia/Nicosia", "Asia/Novokuznetsk", "Asia/Novosibirsk", "Asia/Omsk", "Asia/Oral", "Asia/Phnom Penh", "Asia/Pontianak", "Asia/Pyongyang", "Asia/Qatar", "Asia/Qostanay", "Asia/Qyzylorda", "Asia/Rangoon", "Asia/Riyadh", "Asia/Saigon", "Asia/Sakhalin", "Asia/Samarkand", "Asia/Seoul", "Asia/Shanghai", "Asia/Singapore", "Asia/Srednekolymsk", "Asia/Taipei", "Asia/Tashkent", "Asia/Tbilisi", "Asia/Tehran", "Asia/Tel Aviv", "Asia/Thimbu", "Asia/Thimphu", "Asia/Tokyo", "Asia/Tomsk", "Asia/Ujung Pandang", "Asia/Ulaanbaatar", "Asia/Ulan Bator", "Asia/Urumqi", "Asia/Ust-Nera", "Asia/Vientiane", "Asia/Vladivostok", "Asia/Yakutsk", "Asia/Yangon", "Asia/Yekaterinburg", "Asia/Yerevan", "Atlantic/Azores", "Atlantic/Bermuda", "Atlantic/Canary", "Atlantic/Cape Verde", "Atlantic/Faeroe", "Atlantic/Faroe", "Atlantic/Jan Mayen", "Atlantic/Madeira", "Atlantic/Reykjavik", "Atlantic/South Georgia", "Atlantic/St Helena", "Atlantic/Stanley", "Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken Hill", "Australia/Canberra", "Australia/Currie", "Australia/Darwin", "Australia/Eucla", "Australia/Hobart", "Australia/LHI", "Australia/Lindeman", "Australia/Lord Howe", "Australia/Melbourne", "Australia/NSW", "Australia/North", "Australia/Perth", "Australia/Queensland", "Australia/South", "Australia/Sydney", "Australia/Tasmania", "Australia/Victoria", "Australia/West", "Australia/Yancowinna", "Brazil/Acre", "Brazil/DeNoronha", "Brazil/East", "Brazil/West", "CET", "CST6CDT", "Canada/Atlantic", "Canada/Central", "Canada/Eastern", "Canada/Mountain", "Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "Chile/Continental", "Chile/EasterIsland", "Cuba", "EET", "EST", "EST5EDT", "Egypt", "Eire", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", "Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", "Etc/GMT+5", "Etc/GMT+6", "Etc/GMT+7", "Etc/GMT+8", "Etc/GMT+9", "Etc/GMT-0", "Etc/GMT-1", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12", "Etc/GMT-13", "Etc/GMT-14", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7", "Etc/GMT-8", "Etc/GMT-9", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "Europe/Amsterdam", "Europe/Andorra", "Europe/Astrakhan", "Europe/Athens", "Europe/Belfast", "Europe/Belgrade", "Europe/Berlin", "Europe/Bratislava", "Europe/Brussels", "Europe/Bucharest", "Europe/Budapest", "Europe/Busingen", "Europe/Chisinau", "Europe/Copenhagen", "Europe/Dublin", "Europe/Gibraltar", "Europe/Guernsey", "Europe/Helsinki", "Europe/Isle of Man", "Europe/Istanbul", "Europe/Jersey", "Europe/Kaliningrad", "Europe/Kiev", "Europe/Kirov", "Europe/Lisbon", "Europe/Ljubljana", "Europe/London", "Europe/Luxembourg", "Europe/Madrid", "Europe/Malta", "Europe/Mariehamn", "Europe/Minsk", "Europe/Monaco", "Europe/Moscow", "Europe/Nicosia", "Europe/Oslo", "Europe/Paris", "Europe/Podgorica", "Europe/Prague", "Europe/Riga", "Europe/Rome", "Europe/Samara", "Europe/San Marino", "Europe/Sarajevo", "Europe/Saratov", "Europe/Simferopol", "Europe/Skopje", "Europe/Sofia", "Europe/Stockholm", "Europe/Tallinn", "Europe/Tirane", "Europe/Tiraspol", "Europe/Ulyanovsk", "Europe/Uzhgorod", "Europe/Vaduz", "Europe/Vatican", "Europe/Vienna", "Europe/Vilnius", "Europe/Volgograd", "Europe/Warsaw", "Europe/Zagreb", "Europe/Zaporozhye", "Europe/Zurich", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", "HST", "Hongkong", "Iceland", "Indian/Antananarivo", "Indian/Chagos", "Indian/Christmas", "Indian/Cocos", "Indian/Comoro", "Indian/Kerguelen", "Indian/Mahe", "Indian/Maldives", "Indian/Mauritius", "Indian/Mayotte", "Indian/Reunion", "Iran", "Israel", "Jamaica", "Japan", "Kwajalein", "Libya", "MET", "MST", "MST7MDT", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General", "NZ", "NZ-CHAT", "Navajo", "PRC", "PST8PDT", "Pacific/Apia", "Pacific/Auckland", "Pacific/Bougainville", "Pacific/Chatham", "Pacific/Chuuk", "Pacific/Easter", "Pacific/Efate", "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Fiji", "Pacific/Funafuti", "Pacific/Galapagos", "Pacific/Gambier", "Pacific/Guadalcanal", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", "Pacific/Majuro", "Pacific/Marquesas", "Pacific/Midway", "Pacific/Nauru", "Pacific/Niue", "Pacific/Norfolk", "Pacific/Noumea", "Pacific/Pago Pago", "Pacific/Palau", "Pacific/Pitcairn", "Pacific/Pohnpei", "Pacific/Ponape", "Pacific/Port Moresby", "Pacific/Rarotonga", "Pacific/Saipan", "Pacific/Samoa", "Pacific/Tahiti", "Pacific/Tarawa", "Pacific/Tongatapu", "Pacific/Truk", "Pacific/Wake", "Pacific/Wallis", "Pacific/Yap", "Poland", "Portugal", "ROC", "ROK", "Singapore", "Turkey", "UCT", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Pacific-New", "US/Samoa", "UTC", "Universal", "W-SU", "WET", "Zulu"] +const USTZS = ["America/New York", "America/Detroit", "America/Kentucky/Louisville", "America/Kentucky/Monticello", "America/Indiana/Indianapolis", "America/Indiana/Vincennes", "America/Indiana/Winamac", "America/Indiana/Marengo", "America/Indiana/Petersburg", "America/Indiana/Vevay", "America/Chicago", "America/Indiana/Tell City", "America/Indiana/Knox", "America/Menominee", "America/North Dakota/Center", "America/North Dakota/New Salem", "America/North Dakota/Beulah", "America/Denver", "America/Boise", "America/Phoenix", "America/Los Angeles", "America/Anchorage", "America/Juneau", "America/Sitka", "America/Metlakatla", "America/Yakutat", "America/Nome", "America/Adak", "Pacific/Honolulu"] + test('new york date timestamp format', () => { - // Month parameter is zero indexed so it's actually the 10th month. - const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime(); - const expected = 'Saturday, October 3, 2020'; - const actual = utils.timestampToDateFormatted(testDate); - expect(actual).toEqual(expected); + // Month parameter is zero indexed so it's actually the 10th month. + const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime(); + const expected = 'Saturday, October 3, 2020'; + const actual = utils.timestampToDateFormatted(testDate); + expect(actual).toEqual(expected); }); test('other date timestamp format', () => { - const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime(); - const expectedCentral = 'Saturday, August 22, 2020'; - const expectedSingapore = 'Sunday, August 23, 2020'; - const actualCentral = utils.timestampToDateFormatted(testDate, TZ_CHICAGO); - const actualSingapore = utils.timestampToDateFormatted(testDate, TZ_SINGAPORE); - expect(actualCentral).toEqual(expectedCentral); - expect(actualSingapore).toEqual(expectedSingapore); + const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime(); + const expectedCentral = 'Saturday, August 22, 2020'; + const expectedSingapore = 'Sunday, August 23, 2020'; + const actualCentral = utils.timestampToDateFormatted(testDate, TZ_CHICAGO); + const actualSingapore = utils.timestampToDateFormatted(testDate, TZ_SINGAPORE); + expect(actualCentral).toEqual(expectedCentral); + expect(actualSingapore).toEqual(expectedSingapore); }) test('new york time timestamp format', () => { - // Month parameter is zero indexed so it's actually the 10th month. - const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime(); - const expected = '10:19 AM'; - const actual = utils.timestampToTimeFormatted(testDate); - expect(actual).toEqual(expected); + // Month parameter is zero indexed so it's actually the 10th month. + const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime(); + const expected = '10:19 AM'; + const actual = utils.timestampToTimeFormatted(testDate); + expect(actual).toEqual(expected); }); test('other time timestamp format', () => { - const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime(); - const expectedCentral = '9:03 PM'; - const expectedSingapore = '10:03 AM'; - const actualCentral = utils.timestampToTimeFormatted(testDate, TZ_CHICAGO); - const actualSingapore = utils.timestampToTimeFormatted(testDate, TZ_SINGAPORE); - expect(actualCentral).toEqual(expectedCentral); - expect(actualSingapore).toEqual(expectedSingapore); + const testDate = new Date(Date.UTC(2020, 7, 23, 2, 3, 2, 4)).getTime(); + const expectedCentral = '9:03 PM'; + const expectedSingapore = '10:03 AM'; + const actualCentral = utils.timestampToTimeFormatted(testDate, TZ_CHICAGO); + const actualSingapore = utils.timestampToTimeFormatted(testDate, TZ_SINGAPORE); + expect(actualCentral).toEqual(expectedCentral); + expect(actualSingapore).toEqual(expectedSingapore); }) test('new york full timestamp format', () => { - // Month parameter is zero indexed so it's actually the 10th month. - const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime(); - const expected = 'Saturday, October 3, 2020, 10:19 AM'; - const actual = utils.timestampToFormatted(testDate); - expect(actual).toEqual(expected); + // Month parameter is zero indexed so it's actually the 10th month. + const testDate = new Date(Date.UTC(2020, 9, 3, 14, 19, 4, 23)).getTime(); + const expected = 'Saturday, October 3, 2020, 10:19 AM'; + const actual = utils.timestampToFormatted(testDate); + expect(actual).toEqual(expected); }); test('other full timestamp format', () => { @@ -60,6 +63,23 @@ test('other full timestamp format', () => { expect(actualSingapore).toEqual(expectedSingapore); }) +describe('timezones for country', () => { + test('legit country no spaces', () => { + const actual = utils.timezonesForCountry('China'); + const expected = ['Asia/Shanghai', 'Asia/Urumqi']; + expect(new Set(actual.sort())).toEqual(new Set(expected.sort())); + }) + + test('legit country, yes spaces', () => { + const actual = utils.timezonesForCountry('United States of America'); + expect(new Set(actual)).toEqual(new Set(USTZS)); + }) + + test('not legit country (spaces and non spaces)', () => { + const actual = utils.timezonesForCountry('MURICA'); + expect(new Set(actual)).toEqual(new Set(ALLTZS)); + }) +}) const mockTimeNow = 0; jest.mock('firebase/app', () => ({ firestore: { diff --git a/frontend/src/components/ViewActivities/activity.js b/frontend/src/components/ViewActivities/activity.js index 862102ec..9efb4aca 100644 --- a/frontend/src/components/ViewActivities/activity.js +++ b/frontend/src/components/ViewActivities/activity.js @@ -2,18 +2,10 @@ import React from 'react'; import * as time from '../Utils/time.js'; import * as DB from '../../constants/database.js' import '../../styles/activities.css'; -import { getField, writeActivity } from './activityfns.js'; -import { Accordion, Button, Card, Col, Form, Row } from 'react-bootstrap'; +import EditActivity from './editActivity.js'; +import { Accordion, Card } from 'react-bootstrap'; +import * as utils from './activityfns.js'; -/** - * Return a dropdown of all the timezones. - * - * @return {HTML} Dropdown of all the timezones. - */ -function timezonePicker() { - // TODO: Make this dropdown. (#51) - return
-} /** * React component for a single activity. @@ -33,32 +25,6 @@ class Activity extends React.Component { this.setEditActivity = this.setEditActivity.bind(this); this.finishEditActivity = this.finishEditActivity.bind(this); this.displayCard = this.displayCard.bind(this); - this.editActivity = this.editActivity.bind(this); - - // References. - this.editTitleRef = React.createRef(); - this.editStartDateRef = React.createRef(); - this.editEndDateRef = React.createRef(); - this.editStartTimeRef = React.createRef(); - this.editEndTimeRef = React.createRef(); - this.editDescriptionRef = React.createRef(); - } - - /** - * Edit an activity in the database upon form submission. - * TODO: Update times as well! This only does the text field forms (#64). - */ - editActivity() { - let newVals = {}; - if (this.editTitleRef.current.value !== '') { - newVals[DB.ACTIVITIES_TITLE] = this.editTitleRef.current.value; - } - if (this.editDescriptionRef.current.value !== '') { - newVals[DB.ACTIVITIES_DESCRIPTION] = this.editDescriptionRef.current.value; - } - if (Object.keys(newVals).length !== 0) { - writeActivity(this.props.activity.tripId, this.props.activity.id, newVals); - } } /** @@ -75,8 +41,6 @@ class Activity extends React.Component { */ finishEditActivity(event) { this.setState({editing: false}); - event.preventDefault(); - this.editActivity(); }; /** @@ -87,40 +51,15 @@ class Activity extends React.Component { if (!this.state.editing) { // View mode. return (Start time: {time.timestampToFormatted(activity[DB.ACTIVITIES_START_TIME])}
-End time: {time.timestampToFormatted(activity[DB.ACTIVITIES_END_TIME])}
+{utils.getField(activity, DB.ACTIVITIES_DESCRIPTION, '')}
+Start time: {time.timestampToFormatted(activity[DB.ACTIVITIES_START_TIME])} + {utils.getField(activity, DB.ACTIVITIES_START_COUNTRY, '', ' at ')}
+End time: {time.timestampToFormatted(activity[DB.ACTIVITIES_END_TIME])} + {utils.getField(activity, DB.ACTIVITIES_END_COUNTRY, '', ' at ')}
['MM/DD/YYYY', [activities on that day]]
+ * ['MM/DD/YYYY', [activities on that day]]
* @typedef {Array.a and b in display order.
+ * Puta andb in display order.
* This function is a comparator.
* @param {ActivityInfo} a Dictionary representing activity a and its fields.
* @param {ActivityInfo} b Dictionary representing activity b and its fields.
@@ -77,13 +77,14 @@ export function compareActivities(a, b) {
* @param {ActivityInfo} activity The activity from which to get the field.
* @param {string} fieldName Name of field to get.
* @param {*} defaultValue Value if field is not found/is null.
+ * @param {string} prefix The prefix to put before a returned value if the field exists.
* @return {*} activity[fieldName] if possible, else defaultValue.
*/
-export function getField(activity, fieldName, defaultValue) {
+export function getField(activity, fieldName, defaultValue, prefix=''){
if (activity[fieldName] === null || activity[fieldName] === undefined) {
return defaultValue;
}
- return activity[fieldName];
+ return prefix + activity[fieldName];
}
/**
diff --git a/frontend/src/components/ViewActivities/activityfns.test.js b/frontend/src/components/ViewActivities/activityfns.test.js
index 99d82ade..1295b5d8 100644
--- a/frontend/src/components/ViewActivities/activityfns.test.js
+++ b/frontend/src/components/ViewActivities/activityfns.test.js
@@ -96,4 +96,6 @@ test('getField', () => {
const activity = {field1: 'yes'};
expect(activityFns.getField(activity, 'field1', 'nooo')).toBe('yes');
expect(activityFns.getField(activity, 'field2', 4)).toBe(4);
+ expect(activityFns.getField(activity, 'field1', 'nooo', 'aww ')).toBe('aww yes');
+ expect(activityFns.getField(activity, 'field2', 4, ' and')).toBe(4);
})
\ No newline at end of file
diff --git a/frontend/src/components/ViewActivities/editActivity.js b/frontend/src/components/ViewActivities/editActivity.js
new file mode 100644
index 00000000..1c6b7403
--- /dev/null
+++ b/frontend/src/components/ViewActivities/editActivity.js
@@ -0,0 +1,181 @@
+import React from 'react';
+import { Button, Col, Form, Row } from 'react-bootstrap';
+import { getField, writeActivity } from './activityfns.js';
+import * as DB from '../../constants/database.js'
+import { countryList } from '../../constants/countries.js';
+import * as time from '../Utils/time.js';
+import * as formElements from './editActivityFormElements.js';
+
+/**
+ * React component for the form that's used when the user is editing an activity.
+ *
+ * @property {Object} props ReactJS props.
+ * @property {ActivityInfo} props.activity The activity to display.
+ * @property {function} props.submitFunction The function to run upon submission.
+ */
+class EditActivity extends React.Component {
+ /** @override */
+ constructor(props){
+ super(props);
+
+ this.state = {startTz: false, endTz: false};
+
+ // Bind state users/modifiers to `this`.
+ this.editActivity = this.editActivity.bind(this);
+ this.finishEditActivity = this.finishEditActivity.bind(this);
+ this.timezoneDropdown = this.timezoneDropdown.bind(this);
+
+ // References.
+ this.editTitleRef = React.createRef();
+ this.editStartDateRef = React.createRef();
+ this.editEndDateRef = React.createRef();
+ this.editStartTimeRef = React.createRef();
+ this.editEndTimeRef = React.createRef();
+ this.editDescriptionRef = React.createRef();
+ this.editStartLocRef = React.createRef();
+ this.editEndLocRef = React.createRef();
+ this.startTz = React.createRef();
+ this.endTz = React.createRef();
+ }
+
+ /**
+ * Edit an activity in the database upon form submission.
+ * TODO: Update times as well! This only does the text field forms (#64).
+ */
+ editActivity() {
+ let newVals = {};
+ if (this.editTitleRef.current.value !== '') {
+ newVals[DB.ACTIVITIES_TITLE] = this.editTitleRef.current.value;
+ }
+ if (this.editDescriptionRef.current.value !== '') {
+ newVals[DB.ACTIVITIES_DESCRIPTION] = this.editDescriptionRef.current.value;
+ }
+ if (this.editStartLocRef.current.value !== 'No Change'){
+ newVals[DB.ACTIVITIES_START_COUNTRY] = this.editStartLocRef.current.value;
+ }
+ if (this.editEndLocRef.current.value !== 'No Change'){
+ newVals[DB.ACTIVITIES_END_COUNTRY] = this.editEndLocRef.current.value;
+ }
+ if (Object.keys(newVals).length !== 0) {
+ writeActivity(this.props.activity.tripId, this.props.activity.id, newVals);
+ }
+ }
+
+ /** Runs when the `submit` button on the form is pressed. */
+ finishEditActivity(event) {
+ event.preventDefault();
+ this.editActivity();
+ this.props.submitFunction();
+ }
+
+ // "Flip switch" on timezone dropdown so the dropdown's contents update to the
+ // selected country's timezones.
+ startTimeTzUpdate = () => { this.setState({startTz : !this.state.startTz})};
+ endTimeTzUpdate = () => { this.setState({endTz : !this.state.endTz})};
+
+ /**
+ * Returns a dropdown of all the timezones.
+ * The dropdown's values change based on the corrresponding country dropdown to
+ * reduce scrolling and ensure that the location corresponds to the time zone.
+ *
+ * Tests done manually using UI.
+ *
+ * @param {string} st Either 'start' or 'end' depending on whether the
+ * timezone is for the start or end timezone.
+ * @return {HTML} HTML dropdown item.
+ */
+ timezoneDropdown(st) {
+ const ref = st === 'start' ? this.editStartLocRef : this.editEndLocRef;
+ const dbEntry = st === 'start' ? DB.ACTIVITIES_START_COUNTRY : DB.ACTIVITIES_END_COUNTRY;
+ let timezones;
+ if (ref.current == null) {
+ // If activity[key] DNE, then timezones will just return all tzs anyway.
+ timezones = time.timezonesForCountry(this.props.activity[dbEntry]);
+ } else {
+ timezones = time.timezonesForCountry(ref.current.value);
+ }
+ return (
+