Skip to content

Commit

Permalink
💄 CMS UI | Updating the Events Component Editor (#2288)
Browse files Browse the repository at this point in the history
* adding in react-datetime

* adding in the hardcoded list of IANA timezone to city name mappings, to give offsets some context

* updating the events component by injecting and determining the cities in the offset options list, and also the use of a the time picker for the CMS window

* auto-linting stuff and fields added from experimentation (no visual changes

* linting error fix

* offset hour linting issue – truncation option not recognised

* trying new ts ignore locations...

* realised I didn't even need the format and can just use trunc

* updating the UI message

* updating comments and labels

* updating pnpm lock

* fixes
  • Loading branch information
isaaclombardssw authored and Calinator444 committed Nov 12, 2024
1 parent a6f90fc commit 9b8e635
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 41 deletions.
82 changes: 53 additions & 29 deletions components/blocks/Events.template.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,53 @@
import type { Template } from 'tinacms'
import { NumberField, NumberFieldPlugin, NumberInput, TextField, wrapFieldsWithMeta } from 'tinacms'
import React from 'react'
import moment from "moment-timezone";
import React, { useEffect, useState } from 'react';
import Datetime from 'react-datetime';
import "react-datetime/css/react-datetime.css";
import type { Template } from 'tinacms';
import { TextField, wrapFieldsWithMeta } from 'tinacms';
import majorTimezones from './EventsTimezones.json';

const formatTimezoneOption = (value, prefix = "+") => {
return { value: value, label: `GMT ${prefix}${Math.floor(value)}:${value % 1 ? "3" : "0"}0` }
}
type offset = { value: any; label: string; };

const positiveTimezoneList = Array.from(Array(29).keys()).map(value => formatTimezoneOption(value / 2)).reverse()
const negativeTimezoneList = Array.from(Array(24).keys()).map(value =>
{
const tempOption = formatTimezoneOption((value / 2) + 0.5, '-')
return {value: tempOption.value * -1, label: tempOption.label}
})

const timeFormat = Intl.DateTimeFormat('en-US', {
//Basically the gist of most of this processing is to create the list of possible GMT offsets...
//then associate relevant cities to them via the moment-timezone library.

const dateFormat = Intl.DateTimeFormat('en-US', {
year: "numeric",
month: "short",
day: "numeric",
timeZone: "UTC"
});

const timezoneValidation = (value, data) => {
if (value > 23 || value < 0) {
return "The time should be between 0 (00:00) and 23 (23:00)"
}
if (value && value % 1 != 0) {
return "Only whole numbers should be used."
}
const timeFormat = Intl.DateTimeFormat('en-US', {
hour: "numeric",
minute: "numeric",
timeZone: "UTC"
});

const positiveTimezoneList = Array.from(Array(29).keys()).map(value => value / 2).reverse();
const negativeTimezoneList = Array.from(Array(24).keys()).map(value => ((value / 2) + 0.5 ) * -1);

const addCitiesAndPrefix = (offsets: number[], prefix = "+"): offset[] => {
const cityTimezoneMap = new Map();
//Get the timezones of major cities
majorTimezones.forEach(
(cityOffset) => {
const zone = moment.tz(cityOffset.ianaName).utcOffset() / 60
return cityTimezoneMap.set(zone, cityTimezoneMap.get(zone) ? `${cityTimezoneMap.get(zone)}, ${cityOffset.city}` : cityOffset.city)
});
//Concat the city names to the offset array
return offsets.map((offset) => {
const cities = cityTimezoneMap.get(offset);
const displayOffset = Math.trunc(offset);
return {
value: offset,
label: `GMT ${offset > 0 ? "+" : ""}${displayOffset}:${offset % 1 ? "3" : "0"}0` + (`${cities ? `, ${cities}` : ''}`)
}
})
}

//Events schema
export const eventsTemplate: Template = {
label: 'Events',
name: 'events',
Expand Down Expand Up @@ -60,18 +79,23 @@ export const eventsTemplate: Template = {
'Enter date in the timezone of the event.',
ui: {
utc: true,
format: (value, name, field) => value && timeFormat.format(new Date(Date.parse(value)))
format: (value, name, field) => value && dateFormat.format(new Date(Date.parse(value)))
},
},
{
name: 'startTime',
label: 'Start Time',
type: 'number',
type: 'string',
description:
"Enter start time in the timezone of the event. (e.g. if the event starts at 9:00am, enter '9')",
"Enter start time in the timezone of the event. E.g. '9:00 AM' if the event starts at 9 in the location it's being held.",
ui: {
step: 1,
validate: timezoneValidation
format: (value, name, field) => value && timeFormat.format(new Date(Date.parse(value))),
component: wrapFieldsWithMeta(({ field, input, meta }) => {
return <div>
<Datetime dateFormat={false} {...input} utc={true}></Datetime>
</div>

})
},
},
{
Expand All @@ -82,7 +106,7 @@ export const eventsTemplate: Template = {
'Note this field is not mandatory. Leave blank for a 1 day event. Enter date in the timezone of the event.',
ui: {
utc: true,
format: (value, name, field) => value && timeFormat.format(new Date(Date.parse(value)))
format: (value, name, field) => value && dateFormat.format(new Date(Date.parse(value)))
},
},
{
Expand All @@ -93,7 +117,7 @@ export const eventsTemplate: Template = {
description:
'This is locked to midnight on the end date of the event.',
ui: {
format: (value) => "11:59pm",
format: (value) => "11:59 PM",
component: (props) => {
return <div className="mb-4 relative">
<div className="z-50 absolute cursor-not-allowed w-full h-full top-0 left-0"/>
Expand All @@ -114,8 +138,8 @@ export const eventsTemplate: Template = {
parse: (value) => Number(value),
component: 'select',
options: [
...positiveTimezoneList,
...negativeTimezoneList
...addCitiesAndPrefix(positiveTimezoneList),
...addCitiesAndPrefix(negativeTimezoneList, "")
]
}
},
Expand Down
14 changes: 12 additions & 2 deletions components/blocks/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { format } from 'date-fns';
import Image from 'next/image';
import Link from 'next/link';
import React, { Suspense, useEffect, useRef, useState } from 'react';
import { FaCheck, FaChevronRight } from 'react-icons/fa';
import { FaChevronRight } from 'react-icons/fa';

const LazyGlobe = React.lazy(() => import('../ui/Globe'));

Expand Down Expand Up @@ -49,13 +49,23 @@ const Card = ({ cardItem, onHover }) => {
return '';
};

let startTimeDate = new Date(cardItem.startTime);
//This is to debug an issue with interaction between react-datetime (time picker) and tina.
//The date is locally stored as a number (timestamp), even when the saved value in /content is the expected (i.e. a string).
//This is to handle the local case until the user refreshes.
if (startTimeDate.toString() === 'Invalid Date') {
startTimeDate = new Date(+cardItem.startTime)
}

const startTime = startTimeDate.getUTCHours() + (startTimeDate.getUTCMinutes() / 60);

//Gets the accurate start date-time in UTC, by applying the offset and event start time.
//Note that getting UTC minutes is actually getting the time in the event timezone, based on how the values are being stored.
const startDateUTC = new Date(Date.parse(cardItem.startDate));
startDateUTC.setUTCMinutes(
startDateUTC.getUTCMinutes() +
cardItem.timezone * -60 +
cardItem.startTime * 60
startTime * 60
);
//Gets the provided end date at midnight in UTC, or for one day events the start date is re-used.
const endDateUTC = new Date(
Expand Down
20 changes: 20 additions & 0 deletions components/blocks/EventsTimezones.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{ "ianaName": "America/New_York", "city": "New York" },
{ "ianaName": "America/Los_Angeles", "city": "Los Angeles" },
{ "ianaName": "America/Chicago", "city": "Chicago" },
{ "ianaName": "Europe/London", "city": "London" },
{ "ianaName": "Asia/Tokyo", "city": "Tokyo" },
{ "ianaName": "Australia/Sydney", "city": "Sydney" },
{ "ianaName": "Asia/Kolkata", "city": "Kolkata" },
{ "ianaName": "Africa/Johannesburg", "city": "Johannesburg" },
{ "ianaName": "Pacific/Auckland", "city": "Auckland" },
{ "ianaName": "America/Toronto", "city": "Toronto" },
{ "ianaName": "America/Mexico_city", "city": "Mexico city" },
{ "ianaName": "Europe/Paris", "city": "Paris" },
{ "ianaName": "Europe/Moscow", "city": "Moscow" },
{ "ianaName": "Asia/Singapore", "city": "Singapore" },
{ "ianaName": "Asia/Hong_Kong", "city": "Hong Kong" },
{ "ianaName": "Asia/Dubai", "city": "Dubai" },
{ "ianaName": "America/Sao_Paulo", "city": "Sao Paulo" },
{ "ianaName": "America/Argentina/Buenos_Aires", "city": "Buenos Aires" }
]
9 changes: 6 additions & 3 deletions content/blocksPages/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"src": "/img/docs/introduction/homepage-demo-2.webm",
"_template": "video"
}
]
],
"isReversed": false,
"imageBackground": false
}
],
"_template": "features"
Expand Down Expand Up @@ -440,7 +442,7 @@
{
"headline": "NDC Porto",
"startDate": "2024-10-14T00:00:00.000Z",
"startTime": 9,
"startTime": "2024-10-02T09:00:00.000Z",
"endDate": "2024-10-18T00:00:00.000Z",
"timezone": 1,
"location": "Porto, Portugal",
Expand All @@ -452,6 +454,7 @@
{
"headline": "CPH Dev Fest",
"startDate": "2024-08-26T00:00:00.000Z",
"startTime": "2024-10-01T13:00:00.000Z",
"endDate": "2024-08-30T00:00:00.000Z",
"timezone": 2,
"location": "Copenhagen, Denmark",
Expand All @@ -463,7 +466,7 @@
{
"headline": "NDC Oslo",
"startDate": "2024-05-19T00:00:00.000Z",
"startTime": 9,
"startTime": "2024-10-14T09:00:00.000Z",
"endDate": "2024-05-23T00:00:00.000Z",
"timezone": 2,
"location": "Oslo, Norway",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"markdown-toc": "^1.2.0",
"moment": "^2.24.0",
"moment-locales-webpack-plugin": "^1.2.0",
"moment-timezone": "^0.5.45",
"next": "^14.0.3",
"next-seo": "^4.1.0",
"next-svgr": "0.0.2",
Expand All @@ -68,6 +69,7 @@
"raw-loader": "^4.0.0",
"react": "^18.2.0",
"react-animate-height": "^3.1.0",
"react-datetime": "^3.2.0",
"react-dismissible": "^1.1.3",
"react-dom": "^18.2.0",
"react-github-btn": "^1.4.0",
Expand Down
8 changes: 4 additions & 4 deletions pages/[slug].tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { client } from '../tina/__generated__/client'
import { GetStaticProps, GetStaticPaths } from 'next'
import { fileToUrl } from 'utils/urls'
import { useTina } from 'tinacms/dist/react'
import { BlocksPage } from 'components/blocks/BlocksPage'
import { GetStaticPaths, GetStaticProps } from 'next'
import { useTina } from 'tinacms/dist/react'
import { fileToUrl } from 'utils/urls'
import { client } from '../tina/__generated__/client'

const fg = require('fast-glob')

Expand Down
22 changes: 20 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tina/tina-lock.json

Large diffs are not rendered by default.

0 comments on commit 9b8e635

Please sign in to comment.