commit 6c66dc8a7c14a41bc6ea3e841ca4dc09a0549041
parent 78612fe68242d0e9ae9348f14933faf91a7b4f11
Author: Agastya Chandrakant <acagastya@outlook.com>
Date:   Mon, 25 May 2020 21:36:16 +0530
Add Future; saving pending
Diffstat:
5 files changed, 265 insertions(+), 3 deletions(-)
diff --git a/src/App/comps/ErrorAlert.tsx b/src/App/comps/ErrorAlert.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+function ErrorAlert({ msg }: { msg: string }) {
+  return (
+    <div
+      className="alert alert-warning alert-dismissible fade show mt-5"
+      role="alert"
+    >
+      {msg}
+      <button
+        type="button"
+        className="close"
+        data-dismiss="alert"
+        aria-label="Close"
+      >
+        <span aria-hidden="true">×</span>
+      </button>
+    </div>
+  );
+}
+
+export default ErrorAlert;
diff --git a/src/App/index.tsx b/src/App/index.tsx
@@ -2,6 +2,7 @@ import React from 'react';
 import { Route, BrowserRouter as Router, Switch } from 'react-router-dom';
 
 import DualConvertor from './pages/DualConvertor';
+import FutureConversion from './pages/FutureConversion';
 import Help from './pages/Help';
 import SimpleConvertor from './pages/SimpleConvertor';
 
@@ -24,6 +25,18 @@ function App<never>(): JSX.Element {
       >
         <Header />
         <Switch>
+          <Route
+            path="/future"
+            render={() => (
+              <FutureConversion
+                time={now}
+                setTZ1={setTimezone1}
+                setTZ2={setTimezone2}
+                TZ1={timezone1}
+                TZ2={timezone2}
+              />
+            )}
+          />
           <Route path="/help" render={() => <Help time={now} />} />
           <Route
             path="/from-to"
diff --git a/src/App/pages/FutureConversion.tsx b/src/App/pages/FutureConversion.tsx
@@ -0,0 +1,206 @@
+import React from 'react';
+import moment from 'moment-timezone';
+
+import TimezoneInput from '../comps/TimezoneInput';
+
+import {
+  displayTime,
+  friendlyStr,
+  getAbbr,
+  timezoneList,
+  HMMDY,
+  HMSDMY,
+} from '../../utils';
+import { HM, MAX_DATE, YMD, localTimezone } from '../../utils';
+import { IFutureConversion, IFutureConverted } from '../../utils/interfaces';
+import ErrorAlert from '../comps/ErrorAlert';
+
+function FutureConversion({
+  time: now,
+  setTZ1,
+  setTZ2,
+  TZ1,
+  TZ2,
+}: IFutureConversion): JSX.Element {
+  //
+  const [date, setDate] = React.useState(
+    displayTime({ fmtStr: YMD, time: now, timezone: localTimezone })
+  );
+  const [time, setTime] = React.useState(
+    displayTime({ fmtStr: HM, time: now, timezone: localTimezone })
+  );
+
+  const [selectedTime, setSelectedTime] = React.useState<
+    moment.Moment | undefined
+  >();
+  const [err, setErr] = React.useState<string>('');
+
+  function handleDateChange(e: React.ChangeEvent<HTMLInputElement>): void {
+    setDate(e.target.value);
+    setSelectedTime(undefined);
+  }
+
+  function handleFormSubmit(e: React.FormEvent<HTMLFormElement>): void {
+    e.preventDefault();
+
+    // 0. Reset Error and selected time
+    setErr('');
+    setSelectedTime(undefined);
+    // 1. Validate selected date
+    const selectedDate = moment(date);
+    if (!selectedDate.isValid()) {
+      setErr('Chosen date is not valid.');
+      console.warn(err);
+      return;
+    }
+    // 2. Check limit of selected date
+    // 2.1 MAX allowed
+    const epoch = moment(MAX_DATE, YMD);
+    if (epoch.unix() - selectedDate.unix() < 0) {
+      setErr('Chosen date is outside the maximum permissible limit.');
+      console.warn(err);
+      return;
+    }
+    // 2.2 MIN allowed
+    const todayMoment = moment(now.format(YMD), YMD);
+    const selectedMoment = moment(selectedDate.format(YMD), YMD);
+    if (selectedMoment.unix() - todayMoment.unix() < 0) {
+      setErr('Chosen date is in the past.');
+      console.warn(err);
+      return;
+    }
+    // 3. Validate chosen time
+    const dateStr = selectedDate.format(YMD);
+    const timeStr = time;
+    const dateTimeStr = `${dateStr} ${timeStr}`;
+    const dateTime = moment.tz(dateTimeStr, TZ1);
+    if (!dateTime.isValid()) {
+      setErr('Error occurred while parsing time.');
+      console.warn(err);
+      return;
+    }
+    // 4. validate chosen timezone
+    if (timezoneList.indexOf(TZ1) < 0) {
+      setErr('Selected timezone is invalid.');
+      console.warn(err);
+      return;
+    }
+    // 5. validate set timezone
+    if (timezoneList.indexOf(TZ2) < 0) {
+      setErr('Selected timezone to convert is invalid.');
+      console.warn(err);
+      return;
+    }
+    // 6. convert time
+    setSelectedTime(dateTime);
+  }
+
+  function handleTimeChange(e: React.ChangeEvent<HTMLInputElement>): void {
+    setTime(e.target.value);
+    setSelectedTime(undefined);
+  }
+
+  return (
+    <div className="container">
+      <form onSubmit={handleFormSubmit}>
+        <div className="form-group">
+          <label htmlFor="choose-date">Choose date</label>
+          <input
+            aria-describedby="choose-date"
+            className="form-control"
+            id="chosen-date"
+            max={MAX_DATE}
+            min={displayTime({
+              fmtStr: YMD,
+              time: now,
+              timezone: localTimezone,
+            })}
+            name="choose-date"
+            onChange={handleDateChange}
+            placeholder="Enter date"
+            type="date"
+            value={date}
+          />
+        </div>
+        <div className="form-group">
+          <label htmlFor="choose-time">Choose time</label>
+          <input
+            aria-describedby="choose-time"
+            className="form-control"
+            id="choose-time"
+            name="choose-time"
+            onChange={handleTimeChange}
+            placeholder="Enter time"
+            type="time"
+            value={time}
+          />
+        </div>
+        <div className="form-group">
+          <label htmlFor="choose-from-timezone">Set timezone</label>
+          <TimezoneInput
+            autofocus={false}
+            changeValue={setTZ1}
+            clearInput={setSelectedTime}
+            TZ={TZ1}
+            id="choose-from-timezone"
+            placeholder="Set timezone"
+          />
+        </div>
+        <div className="form-group">
+          <label htmlFor="convert-to-timezone">Convert to timezone</label>
+          <TimezoneInput
+            autofocus={false}
+            changeValue={setTZ2}
+            clearInput={setSelectedTime}
+            TZ={TZ2}
+            id="convert-to-timezone"
+            placeholder="Convert to timezone"
+          />
+        </div>
+        <button type="submit" className="btn btn-success">
+          Convert
+        </button>
+        {err ? <ErrorAlert msg={err} /> : null}
+        {selectedTime !== undefined ? (
+          <FutureConverted selectedTime={selectedTime} TZ1={TZ1} TZ2={TZ2} />
+        ) : null}
+      </form>
+    </div>
+  );
+}
+
+function FutureConverted({
+  selectedTime,
+  TZ1,
+  TZ2,
+}: IFutureConverted): JSX.Element {
+  // console.log(selectedTime);
+  const chosenTime = selectedTime;
+  const convertedTime = selectedTime.clone().tz(TZ2);
+  const fromLbl = getAbbr({ timezone: TZ1, time: moment(chosenTime, HMSDMY) });
+  const toLbl = getAbbr({ timezone: TZ2, time: moment(convertedTime, HMSDMY) });
+  return (
+    <div className="mt-5">
+      <div className="row">
+        <div className="col">
+          <h1>{friendlyStr(TZ1)}</h1>
+        </div>
+        <div className="col">
+          <h1>{friendlyStr(TZ2)}</h1>
+        </div>
+      </div>
+      <div className="row">
+        <div className="col">
+          {displayTime({ fmtStr: HMMDY, time: chosenTime, timezone: TZ1 })} (
+          {fromLbl})
+        </div>
+        <div className="col">
+          {displayTime({ fmtStr: HMMDY, time: convertedTime, timezone: TZ2 })} (
+          {toLbl})
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default FutureConversion;
diff --git a/src/utils/index.ts b/src/utils/index.ts
@@ -37,7 +37,7 @@ function friendlyStr(str: string): string {
  */
 function getAbbr({ timezone, time }: { timezone: string, time: moment.Moment }): string {
   // @ts-ignore
-  return moment.tz.zone(timezone).abbr(time.unix());
+  return moment.tz.zone(timezone).abbr(time);
 }
 
 /**
@@ -103,15 +103,23 @@ export {
 };
 
 const DEFAULT_TZ = 'UTC';
-const HMSDMY = 'HH:mm:ss MMMM, DD, YYYY';
+const HM = 'HH:mm';
+const HMMDY = 'HH:mm MMMM DD, YYYY';
+const HMSDMY = 'HH:mm:ss MMMM DD, YYYY';
 const localTimezone = moment.tz.guess();
+const MAX_DATE = '2038-01-18';
 const timezoneList = moment.tz.names().sort();
 const YEAR = new Date().getFullYear();
+const YMD = 'YYYY-MM-DD'
 
 export {
   DEFAULT_TZ,
+  HM,
+  HMMDY,
   HMSDMY,
   localTimezone,
+  MAX_DATE,
   timezoneList,
-  YEAR
+  YEAR,
+  YMD
 };
 \ No newline at end of file
diff --git a/src/utils/interfaces.tsx b/src/utils/interfaces.tsx
@@ -14,6 +14,19 @@ export interface IDisplayTime {
   timezone: string;
 }
 
+export interface IFutureConversion {
+  time: moment.Moment;
+  setTZ1: React.Dispatch<React.SetStateAction<string>>;
+  setTZ2: React.Dispatch<React.SetStateAction<string>>;
+  TZ1: string;
+  TZ2: string;
+}
+export interface IFutureConverted {
+  selectedTime: moment.Moment;
+  TZ1: string;
+  TZ2: string;
+}
+
 export interface IMoment {
   time: moment.Moment;
 }