From a3666930a09d98ed73e1a03e65c2df92258a6938 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Wed, 12 May 2021 21:27:00 +1000 Subject: [PATCH 1/4] Event ID conflict management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤜💥🤛 --- crabfit-backend/routes/createEvent.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/crabfit-backend/routes/createEvent.js b/crabfit-backend/routes/createEvent.js index 08562f3..0323e2d 100644 --- a/crabfit-backend/routes/createEvent.js +++ b/crabfit-backend/routes/createEvent.js @@ -20,10 +20,23 @@ module.exports = async (req, res) => { try { const name = event.name.trim() === '' ? generateName() : event.name.trim(); - const eventId = generateId(name); + let eventId = generateId(name); const currentTime = dayjs().unix(); - const entity = { + let eventResult; + do { + const query = req.datastore.createQuery(req.types.event) + .select('__key__') + .filter('__key__', req.datastore.key([req.types.event, eventId])); + + eventResult = (await req.datastore.runQuery(query))[0][0]; + + if (eventResult !== undefined) { + eventId = generateId(name); + } + } while (eventResult !== undefined); + + const entity = { key: req.datastore.key([req.types.event, eventId]), data: { name: name, From 87b36e417df0b8fc082461232106a85fab4322a7 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Wed, 12 May 2021 21:48:57 +1000 Subject: [PATCH 2/4] Timezone helper links --- crabfit-frontend/src/pages/Event/Event.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crabfit-frontend/src/pages/Event/Event.tsx b/crabfit-frontend/src/pages/Event/Event.tsx index a0184e1..cfb415b 100644 --- a/crabfit-frontend/src/pages/Event/Event.tsx +++ b/crabfit-frontend/src/pages/Event/Event.tsx @@ -366,6 +366,22 @@ const Event = (props) => { onChange={event => setTimezone(event.currentTarget.value)} options={timezones} /> + {event?.timezone && event.timezone !== timezone &&

This event was created in the timezone {event.timezone}. { + e.preventDefault(); + setTimezone(event.timezone); + }}>Click here to use it.

} + {(( + Intl.DateTimeFormat().resolvedOptions().timeZone !== timezone + && (event?.timezone && event.timezone !== Intl.DateTimeFormat().resolvedOptions().timeZone) + ) || ( + event?.timezone === undefined + && Intl.DateTimeFormat().resolvedOptions().timeZone !== timezone + )) && ( +

Your local timezone is detected to be {Intl.DateTimeFormat().resolvedOptions().timeZone}. { + e.preventDefault(); + setTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone); + }}>Click here to use it.

+ )} From a73eb76d43dc31ef70f8126d2ef496a66f95f958 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Wed, 12 May 2021 22:05:43 +1000 Subject: [PATCH 3/4] Event created date --- crabfit-frontend/src/pages/Event/Event.tsx | 4 ++++ .../src/pages/Event/eventStyle.ts | 24 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crabfit-frontend/src/pages/Event/Event.tsx b/crabfit-frontend/src/pages/Event/Event.tsx index cfb415b..52ea2f1 100644 --- a/crabfit-frontend/src/pages/Event/Event.tsx +++ b/crabfit-frontend/src/pages/Event/Event.tsx @@ -6,6 +6,7 @@ import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; import customParseFormat from 'dayjs/plugin/customParseFormat'; +import relativeTime from 'dayjs/plugin/relativeTime'; import { Center, @@ -23,6 +24,7 @@ import { Logo, Title, EventName, + EventDate, LoginForm, LoginSection, Info, @@ -40,6 +42,7 @@ import timezones from 'res/timezones.json'; dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(customParseFormat); +dayjs.extend(relativeTime); const Event = (props) => { const timeFormat = useSettingsStore(state => state.timeFormat); @@ -283,6 +286,7 @@ const Event = (props) => { {(!!event || isLoading) ? ( <> {event?.name} + {event?.created && `Created ${dayjs.unix(event?.created).fromNow()}`} navigator.clipboard?.writeText(`https://crab.fit/${id}`) .then(() => { diff --git a/crabfit-frontend/src/pages/Event/eventStyle.ts b/crabfit-frontend/src/pages/Event/eventStyle.ts index f5f3354..2727b99 100644 --- a/crabfit-frontend/src/pages/Event/eventStyle.ts +++ b/crabfit-frontend/src/pages/Event/eventStyle.ts @@ -24,7 +24,7 @@ export const Title = styled.span` export const EventName = styled.h1` text-align: center; font-weight: 800; - margin: 20px 0 14px; + margin: 20px 0 5px; ${props => props.isLoading && ` &:after { @@ -39,6 +39,28 @@ export const EventName = styled.h1` `} `; +export const EventDate = styled.span` + display: block; + text-align: center; + font-size: 14px; + opacity: .8; + margin: 0 0 10px; + font-weight: 500; + letter-spacing: .01em; + + ${props => props.isLoading && ` + &:after { + content: ''; + display: inline-block; + height: 1em; + width: 200px; + max-width: 100%; + background-color: ${props.theme.loading}; + border-radius: 3px; + } + `} +`; + export const LoginForm = styled.form` display: grid; grid-template-columns: 1fr 1fr 100px; From 00b47e2656b3e7e533646cd9c5f020321393e349 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Thu, 13 May 2021 03:22:26 +1000 Subject: [PATCH 4/4] Punycode slug encoding for non-ascii characters --- crabfit-backend/package.json | 3 ++- crabfit-backend/routes/createEvent.js | 21 ++++++++++++++------- crabfit-backend/yarn.lock | 5 +++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/crabfit-backend/package.json b/crabfit-backend/package.json index 885330d..ca93155 100644 --- a/crabfit-backend/package.json +++ b/crabfit-backend/package.json @@ -18,6 +18,7 @@ "cors": "^2.8.5", "dayjs": "^1.10.4", "dotenv": "^8.2.0", - "express": "^4.17.1" + "express": "^4.17.1", + "punycode": "^2.1.1" } } diff --git a/crabfit-backend/routes/createEvent.js b/crabfit-backend/routes/createEvent.js index 0323e2d..9fa0fe1 100644 --- a/crabfit-backend/routes/createEvent.js +++ b/crabfit-backend/routes/createEvent.js @@ -1,20 +1,26 @@ const dayjs = require('dayjs'); +const punycode = require('punycode/'); const adjectives = require('../res/adjectives.json'); const crabs = require('../res/crabs.json'); -const capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1); - -const generateId = (name) => { - const id = name.trim().toLowerCase().replace(/[^A-Za-z0-9 ]/g, '').replace(/\s+/g, '-'); - const number = Math.floor(100000 + Math.random() * 900000); - return `${id}-${number}`; -}; +const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1); +// Generate a random name based on an adjective and a crab species const generateName = () => { return `${capitalize(adjectives[Math.floor(Math.random() * adjectives.length)])} ${crabs[Math.floor(Math.random() * crabs.length)]} Crab`; }; +// Generate a slug for the crab fit +const generateId = name => { + let id = punycode.encode(name.trim().toLowerCase()).trim().replace(/[^A-Za-z0-9 ]/g, '').replace(/\s+/g, '-'); + if (id.replace(/-/g, '') === '') { + id = generateName().trim().toLowerCase().replace(/[^A-Za-z0-9 ]/g, '').replace(/\s+/g, '-'); + } + const number = Math.floor(100000 + Math.random() * 900000); + return `${id}-${number}`; +}; + module.exports = async (req, res) => { const { event } = req.body; @@ -23,6 +29,7 @@ module.exports = async (req, res) => { let eventId = generateId(name); const currentTime = dayjs().unix(); + // Check if the event ID already exists, and if so generate a new one let eventResult; do { const query = req.datastore.createQuery(req.types.event) diff --git a/crabfit-backend/yarn.lock b/crabfit-backend/yarn.lock index 4c35f38..04adf65 100644 --- a/crabfit-backend/yarn.lock +++ b/crabfit-backend/yarn.lock @@ -942,6 +942,11 @@ pumpify@^2.0.1: inherits "^2.0.3" pump "^3.0.0" +punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + qs@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"