Merge pull request #163 from 50bbx/feature/add-timezone-select-in-calendar-view
Add timezone select in calendar view
This commit is contained in:
		
						commit
						60d7351eeb
					
				
					 4 changed files with 62 additions and 54 deletions
				
			
		|  | @ -23,7 +23,7 @@ const getSlots = ({ | ||||||
| 
 | 
 | ||||||
|   if(!selectedDate) return [] |   if(!selectedDate) return [] | ||||||
|    |    | ||||||
|   const lowerBound = selectedDate.startOf("day"); |   const lowerBound = selectedDate.tz(selectedTimeZone).startOf("day"); | ||||||
| 
 | 
 | ||||||
|   // Simple case, same timezone
 |   // Simple case, same timezone
 | ||||||
|   if (calendarTimeZone === selectedTimeZone) { |   if (calendarTimeZone === selectedTimeZone) { | ||||||
|  | @ -42,7 +42,7 @@ const getSlots = ({ | ||||||
|     return slots; |     return slots; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const upperBound = selectedDate.endOf("day"); |   const upperBound = selectedDate.tz(selectedTimeZone).endOf("day"); | ||||||
| 
 | 
 | ||||||
|   // We need to start generating slots from the start of the calendarTimeZone day
 |   // We need to start generating slots from the start of the calendarTimeZone day
 | ||||||
|   const startDateTime = lowerBound |   const startDateTime = lowerBound | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ | ||||||
|     "next-transpile-modules": "^7.0.0", |     "next-transpile-modules": "^7.0.0", | ||||||
|     "react": "17.0.1", |     "react": "17.0.1", | ||||||
|     "react-dom": "17.0.1", |     "react-dom": "17.0.1", | ||||||
|     "react-timezone-select": "^0.10.10" |     "react-timezone-select": "^1.0.2" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/node": "^14.14.33", |     "@types/node": "^14.14.33", | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import prisma from '../../lib/prisma'; | ||||||
| import { useRouter } from 'next/router'; | import { useRouter } from 'next/router'; | ||||||
| import dayjs, { Dayjs } from 'dayjs'; | import dayjs, { Dayjs } from 'dayjs'; | ||||||
| import { Switch } from '@headlessui/react'; | import { Switch } from '@headlessui/react'; | ||||||
|  | import TimezoneSelect from 'react-timezone-select'; | ||||||
| import { ClockIcon, GlobeIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'; | import { ClockIcon, GlobeIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'; | ||||||
| import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; | import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; | ||||||
| import isBetween from 'dayjs/plugin/isBetween'; | import isBetween from 'dayjs/plugin/isBetween'; | ||||||
|  | @ -32,6 +33,14 @@ export default function Type(props) { | ||||||
|     const [busy, setBusy] = useState([]); |     const [busy, setBusy] = useState([]); | ||||||
|     const telemetry = useTelemetry(); |     const telemetry = useTelemetry(); | ||||||
| 
 | 
 | ||||||
|  |     const [selectedTimeZone, setSelectedTimeZone] = useState(''); | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |       // Setting timezone only client-side
 | ||||||
|  |       setSelectedTimeZone(dayjs.tz.guess()) | ||||||
|  |     }, []) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     // Get router variables
 |     // Get router variables
 | ||||||
|     const router = useRouter(); |     const router = useRouter(); | ||||||
|     const { user } = router.query; |     const { user } = router.query; | ||||||
|  | @ -81,13 +90,13 @@ export default function Type(props) { | ||||||
|     const calendar = [...emptyDays, ...days.map((day) => |     const calendar = [...emptyDays, ...days.map((day) => | ||||||
|         <button key={day} onClick={(e) => { |         <button key={day} onClick={(e) => { | ||||||
|             telemetry.withJitsu((jitsu) => jitsu.track('date_selected', {page_title: "", source_ip: ""})) |             telemetry.withJitsu((jitsu) => jitsu.track('date_selected', {page_title: "", source_ip: ""})) | ||||||
|             setSelectedDate(dayjs().tz(dayjs.tz.guess()).month(selectedMonth).date(day)) |             setSelectedDate(dayjs().tz(selectedTimeZone).month(selectedMonth).date(day)) | ||||||
|         }} disabled={selectedMonth < parseInt(dayjs().format('MM')) && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}> |         }} disabled={selectedMonth < parseInt(dayjs().format('MM')) && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}> | ||||||
|             {day} |             {day} | ||||||
|         </button> |         </button> | ||||||
|     )]; |     )]; | ||||||
| 
 | 
 | ||||||
|     // Handle date change
 |     // Handle date change and timezone change
 | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|         const changeDate = async () => { |         const changeDate = async () => { | ||||||
|             if (!selectedDate) { |             if (!selectedDate) { | ||||||
|  | @ -101,17 +110,18 @@ export default function Type(props) { | ||||||
|             setLoading(false); |             setLoading(false); | ||||||
|         } |         } | ||||||
|         changeDate(); |         changeDate(); | ||||||
|     }, [selectedDate]); |     }, [selectedDate, selectedTimeZone]); | ||||||
| 
 | 
 | ||||||
| 
 |     const times = useMemo(() => | ||||||
|     const times = getSlots({ |       getSlots({ | ||||||
|       calendarTimeZone: props.user.timeZone, |         calendarTimeZone: props.user.timeZone, | ||||||
|       selectedTimeZone: dayjs.tz.guess(), |         selectedTimeZone: selectedTimeZone, | ||||||
|       eventLength: props.eventType.length, |         eventLength: props.eventType.length, | ||||||
|       selectedDate: selectedDate, |         selectedDate: selectedDate, | ||||||
|       dayStartTime: props.user.startTime, |         dayStartTime: props.user.startTime, | ||||||
|       dayEndTime: props.user.endTime, |         dayEndTime: props.user.endTime, | ||||||
|     }) |       }) | ||||||
|  |     , [selectedDate, selectedTimeZone]) | ||||||
| 
 | 
 | ||||||
|     // Check for conflicts
 |     // Check for conflicts
 | ||||||
|     for(let i = times.length - 1; i >= 0; i -= 1) { |     for(let i = times.length - 1; i >= 0; i -= 1) { | ||||||
|  | @ -135,7 +145,7 @@ export default function Type(props) { | ||||||
|     const availableTimes = times.map((time) => |     const availableTimes = times.map((time) => | ||||||
|         <div key={dayjs(time).utc().format()}> |         <div key={dayjs(time).utc().format()}> | ||||||
|             <Link href={`/${props.user.username}/book?date=${dayjs(time).utc().format()}&type=${props.eventType.id}`}> |             <Link href={`/${props.user.username}/book?date=${dayjs(time).utc().format()}&type=${props.eventType.id}`}> | ||||||
|                 <a key={dayjs(time).format("hh:mma")} className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">{dayjs(time).tz(dayjs.tz.guess()).format(is24h ? "HH:mm" : "hh:mma")}</a> |                 <a key={dayjs(time).format("hh:mma")} className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">{dayjs(time).tz(selectedTimeZone).format(is24h ? "HH:mm" : "hh:mma")}</a> | ||||||
|             </Link> |             </Link> | ||||||
|         </div> |         </div> | ||||||
|     ); |     ); | ||||||
|  | @ -158,39 +168,37 @@ export default function Type(props) { | ||||||
|                                 <ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> |                                 <ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> | ||||||
|                                 {props.eventType.length} minutes |                                 {props.eventType.length} minutes | ||||||
|                             </p> |                             </p> | ||||||
|                             <button onClick={toggleTimeOptions} className="text-gray-500 mb-1 hover:bg-gray-100 rounded-full px-2 -ml-2 cursor-pointer"> |                             <p  className="text-gray-500 mb-1 px-2 py-1 -ml-2"> | ||||||
|                                 <GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1"/> |                             <GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1"/> | ||||||
|                                 {dayjs.tz.guess()} <ChevronDownIcon className="inline-block w-4 h-4 mb-1" /> |                             <Switch.Group as="span" className=""> | ||||||
|                             </button> |                               <Switch.Label as="span" className="mr-3"> | ||||||
|                             { isTimeOptionsOpen && |                                   <span className="text-sm text-gray-500">am/pm</span> | ||||||
|                             <div className="bg-white rounded shadow p-4 absolute w-72"> |                               </Switch.Label> | ||||||
|                                 <Switch.Group as="div" className="flex items-center"> |                               <Switch | ||||||
|                                     <Switch.Label as="span" className="mr-3"> |                                 checked={is24h} | ||||||
|                                         <span className="text-sm text-gray-500">am/pm</span> |                                 onChange={setIs24h} | ||||||
|                                     </Switch.Label> |                                 className={classNames( | ||||||
|                                     <Switch |                                     is24h ? 'bg-blue-600' : 'bg-gray-200', | ||||||
|                                     checked={is24h} |                                     'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500' | ||||||
|                                     onChange={setIs24h} |                                 )} | ||||||
|                                     className={classNames( |                                 > | ||||||
|                                         is24h ? 'bg-blue-600' : 'bg-gray-200', |                                   <span className="sr-only">Use setting</span> | ||||||
|                                         'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500' |                                   <span | ||||||
|                                     )} |                                       aria-hidden="true" | ||||||
|                                     > |                                       className={classNames( | ||||||
|                                         <span className="sr-only">Use setting</span> |                                       is24h ? 'translate-x-5' : 'translate-x-0', | ||||||
|                                         <span |                                       'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200' | ||||||
|                                             aria-hidden="true" |                                       )} | ||||||
|                                             className={classNames( |                                   /> | ||||||
|                                             is24h ? 'translate-x-5' : 'translate-x-0', |                               </Switch> | ||||||
|                                             'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200' |                               <Switch.Label as="span" className="ml-3"> | ||||||
|                                             )} |                                   <span className="text-sm text-gray-500">24h</span> | ||||||
|                                         /> |                               </Switch.Label> | ||||||
|                                     </Switch> |                             </Switch.Group> | ||||||
|                                     <Switch.Label as="span" className="ml-3"> |                             </p> | ||||||
|                                         <span className="text-sm text-gray-500">24h</span> |                             <p className="mt-1 text-gray-500"> | ||||||
|                                     </Switch.Label> |                                 <TimezoneSelect id="timeZone" value={selectedTimeZone} onChange={({ value }) =>setSelectedTimeZone(value)} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" /> | ||||||
|                                 </Switch.Group> |                             </p> | ||||||
|                             </div> |  | ||||||
|                             } |  | ||||||
|                             <p className="text-gray-600 mt-3 mb-8">{props.eventType.description}</p> |                             <p className="text-gray-600 mt-3 mb-8">{props.eventType.description}</p> | ||||||
|                         </div> |                         </div> | ||||||
|                         <div className={"mt-8 sm:mt-0 " + (selectedDate ? 'sm:w-1/3 border-r sm:px-4' : 'sm:w-1/2 sm:pl-4')}> |                         <div className={"mt-8 sm:mt-0 " + (selectedDate ? 'sm:w-1/3 border-r sm:px-4' : 'sm:w-1/2 sm:pl-4')}> | ||||||
|  | @ -268,4 +276,4 @@ export async function getServerSideProps(context) { | ||||||
|             eventType |             eventType | ||||||
|         }, |         }, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2625,10 +2625,10 @@ react-select@^4.2.1: | ||||||
|     react-input-autosize "^3.0.0" |     react-input-autosize "^3.0.0" | ||||||
|     react-transition-group "^4.3.0" |     react-transition-group "^4.3.0" | ||||||
| 
 | 
 | ||||||
| react-timezone-select@^0.10.10: | react-timezone-select@^1.0.2: | ||||||
|   version "0.10.10" |   version "1.0.2" | ||||||
|   resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-0.10.10.tgz#853aeb73e84fcf00bd01eb57c35f2df1b84e1cc0" |   resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-1.0.2.tgz#37e6d99bc15372bd3f9ebc7541bda6f3a10c852b" | ||||||
|   integrity sha512-PEEQQkiL+fFW3940MmhrX6xNf2VMz16BW2UyF6Mu7jzCv89McwJ93Bp5mqE6ouhLPZSsyTnhjILifsEFUUMuFg== |   integrity sha512-MDv4rmDkop3nZcIH27tLwIuIVovJE6ushmr9r0kR1SSzVErdydV01vI1ch8u4JAAFpnR5lYDt5PBqQW/lUS+Jg== | ||||||
|   dependencies: |   dependencies: | ||||||
|     react-select "^4.2.1" |     react-select "^4.2.1" | ||||||
|     spacetime "^6.14.0" |     spacetime "^6.14.0" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Bailey Pumfleet
						Bailey Pumfleet