koro

an event time scheduler
git clone https://tilde.team/~marisa/repo/koro.git
Log | Files | Refs | README | LICENSE

commit 544bd0ff591d22547ced51dd5f2f4e6b9227ce61
parent 236d8fe42552a48d6df8111ace06716ec493f31b
Author: mokou <mokou@posteo.de>
Date:   Sun, 17 Jan 2021 14:04:59 +0100

Revert "feat: Rename event, add half of times"

This reverts commit 236d8fe42552a48d6df8111ace06716ec493f31b.

Diffstat:
Msrc/App.svelte | 4----
Dsrc/pages/CreateEvent.svelte | 153-------------------------------------------------------------------------------
Dsrc/pages/CreateTimes.svelte | 151------------------------------------------------------------------------------
Msrc/pages/Index.svelte | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
4 files changed, 158 insertions(+), 321 deletions(-)

diff --git a/src/App.svelte b/src/App.svelte @@ -2,8 +2,6 @@ import navaid from 'navaid' import { is24Hrs, isDarkMode } from './stores' import Index from './pages/Index.svelte' - import CreateEvent from './pages/CreateEvent.svelte' - import CreateTimes from './pages/CreateTimes.svelte' import Event from './pages/Event.svelte' import CreatePoll from './pages/CreatePoll.svelte' import Poll from './pages/Poll.svelte' @@ -14,8 +12,6 @@ let routeParams router.on('/', setRoute(Index)) - router.on('/event', setRoute(CreateEvent)) - router.on('/times', setRoute(CreateTimes)) router.on('/poll', setRoute(CreatePoll)) router.on('/updates', setRoute(Log)) router.on('/p/:id', setRoute(Poll)) diff --git a/src/pages/CreateEvent.svelte b/src/pages/CreateEvent.svelte @@ -1,153 +0,0 @@ -<script> - import autocomplete from 'accessible-autocomplete' - import search from 'fuzzysearch' - import day from 'dayjs' - import utc from 'dayjs/plugin/utc' - import isOnline from 'is-online' - import { nanoid } from 'nanoid' - import { onMount } from 'svelte' - import { isDarkMode } from '../stores' - import { pouch, replicateToCouch } from '../couch' - import DateTimeGrid from '../components/DateTimeGrid.svelte' - day.extend(utc) - - document.title = 'Create Event - Koro' - let submitting = false - let success = false - let errorFlash = '' - let durationInput = null - let durationsList = [ - '15 minutes', - '30 minutes', - '45 minutes', - '1 hour', - '2 hours', - '3 hours', - '4 hours', - '5 hours' - ] - let event = { - _id: nanoid(), - name: '', - duration: '', - times: [] - } - let eventLink = '' - - async function submitForm() { - errorFlash = '' - submitting = true - if (!event.name) { - errorFlash = 'Please set a name for your event!' - submitting = false - return - } - - if (!event.duration) { - errorFlash = 'Please set a duration for your event!' - submitting = false - return - } - - if (!durationsList.includes(event.duration)) { - errorFlash = 'The duration must be picked from the provided list!' - submitting = false - return - } - - if (event.times.length === 0) { - errorFlash = 'Please pick at least one event time!' - submitting = false - return - } - - const db = await pouch() - try { - const res = await db.put({ - times: event.times.map((e) => day(e).utc().format()), - ...event - }) - await replicateToCouch() - success = true - eventLink = `https://koro.moe/${res.id}` - } catch (e) { - console.error(e) - } - } - - function durationSource(query, cb) { - if (durationsList.includes(query)) cb([]) - const filtered = durationsList.filter((d) => search(query, d)) - cb(filtered) - } - - onMount(async () => { - if (!(await isOnline())) { - errorFlash = `You're offline! This means you can still create an event, but you'll have to be online before you can share the link with others.` - } - autocomplete({ - element: document.querySelector('#duration-mount'), - id: 'duration', - source: durationSource, - confirmOnBlur: false, - onConfirm: (opt) => { - event.duration = opt - } - }) - }) -</script> - -<h1 class="text-5xl font-serif font-extrabold">Create Event</h1> -{#if success} - <p class="text-2xl font-medium max-w-xl leading-normal"> - Your event has successfully been created! You can share the link below with - anyone you want to invite, and they can set their available times from the - ones you picked out. Don't forget to fill it out yourself! - </p> - - <div - class="text-2xl font-mono bg-black text-white max-w-xl w-full px-3 py-2 mt-5 - text-center"> - {eventLink} - </div> -{:else} - <form class="max-w-xl mt-6" on:submit|preventDefault={submitForm}> - {#if errorFlash} - <div - class="w-full bg-red-100 px-3 py-2 border rounded border-red-400 - text-red-600 my-5"> - {errorFlash} - </div> - {/if} - <label - for="name" - class="font-bold text-gray-300" - class:text-gray-600={!$isDarkMode}> - Your event's name - </label> - <input - id="name" - type="text" - bind:value={event.name} - class:dark={$isDarkMode} - placeholder="Raid with the boys" /> - <label - for="duration" - class="font-bold text-gray-300 inline-block mt-4" - class:text-gray-600={!$isDarkMode}> - Duration of the event - </label> - <div id="duration-mount" class="mb-5" class:dark={$isDarkMode} /> - <label class="font-bold text-gray-300" class:text-gray-600={!$isDarkMode}> - Event start times - </label> - </form> - <DateTimeGrid startDate={new Date()} bind:selected={event.times} /> - <button - class="btn mt-5" - type="submit" - on:click|preventDefault={submitForm} - disabled={submitting}> - Create event - </button> -{/if} diff --git a/src/pages/CreateTimes.svelte b/src/pages/CreateTimes.svelte @@ -1,151 +0,0 @@ -<script> - import autocomplete from 'accessible-autocomplete' - import search from 'fuzzysearch' - import { nanoid } from 'nanoid' - import isOnline from 'is-online' - import { rawTimeZones } from '@vvo/tzdb' - import day from 'dayjs' - import customParseFormat from 'dayjs/plugin/customParseFormat' - import { onMount } from 'svelte' - import { pouch, replicateToCouch } from '../couch' - import { isDarkMode, is24Hrs } from '../stores' - - document.title = 'Create Times - Koro' - day.extend(customParseFormat) - let times = { - _id: `t:${nanoid()}`, - times: [ - - ] - } - let currentTime = { - time: null, - timezone: '' - } - - let timeInput = '' - let timezoneInput = '' - let errorFlash = '' - let success = false - let isValidTime = false - const tzs = rawTimeZones - const tzlist = tzs.map(tz => { - return { str: `${tz.name} (${tz.abbreviation})`, name: tz.name } - }) - - function parseTime (evt) { - const time = evt.target.value - const match = day(time, ['YYYY MM DD HH:mm', 'MM DD HH:mm', 'YYYY MM DD hh:mm', 'MM DD hh:mm'], true) - if (match.isValid()) { - currentTime.time = match.toISOString() - isValidTime = true - } else { - isValidTime = false - } - } - - function addTime () { - const copiedCurrentTime = Object.assign({}, currentTime) - times.times = [...times.times, copiedCurrentTime] - } - - function removeTime (i) { - return () => { - times.times.splice(i, 1) - times = times - } - } - - function submitForm () { - - } - - function timezoneSource (query, cb) { - if (tzlist.includes(query)) db([]) - const filtered = tzlist.filter(d => search(query.toLowerCase(), d.str.toLowerCase())) - cb(filtered) - } - - onMount(async () => { - if (!(await isOnline())) { - errorFlash = `You're offline! This means you can still create times, but you'll have to be online before you can share the link with others.` - } - - autocomplete({ - element: document.querySelector('#timezone-mount'), - id: 'timezone', - source: timezoneSource, - confirmOnBlur: false, - onConfirm: (opt) => { - currentTime.timezone = opt.name - }, - templates: { - inputValue (opt) { - return opt && opt.str - }, - suggestion (s) { - return s.str - } - } - }) - }) -</script> - - -<h1 class="text-5xl font-serif font-extrabold">Create Times</h1> -{#if success} - blah -{:else} - <form class="max-w-xl mt-6" on:submit|preventDefault={submitForm}> - {#if errorFlash} - <div - class="w-full bg-red-100 px-3 py-2 border rounded border-red-400 - text-red-600 my-5"> - {errorFlash} - </div> - {/if} - - <label - for="time" - class="font-bold text-gray-300 inline-block mt-4" - class:text-gray-600={!$isDarkMode}> - Time and Date - </label> - <input - id="time" - on:keyup={parseTime} - class:dark={$isDarkMode} - placeholder="(YYYY) MM DD hh:mm" - type="text"> - <div class:text-red-400={!isValidTime} class:text-green-400={isValidTime}> - {#if isValidTime} - Looks good! - {:else} - Check that you entered the date correctly! - {/if} - </div> - - <label - for="timezone" - class="font-bold text-gray-300 inline-block mt-4" - class:text-gray-600={!$isDarkMode}> - Source Timezone (type to see options) - </label> - <div id="timezone-mount" class="mb-5" class:dark={$isDarkMode} /> - <button - class="btn mt-5" - on:click|preventDefault={addTime} - type="submit"> - Add time - </button> - </form> - - <ul class="list-disc my-3"> - {#each times.times as time, i} - <li> - <span class="hover:cursor-pointer" on:click={removeTime(i)}>❌</span> - <span class="font-bold">{day(time.time).format(`DD/MM/YYYY ${$is24Hrs ? 'HH:mm' : 'hh:mma'}`)}</span> in <span class="font-bold">{tzs.find(tz => tz.name === time.timezone).abbreviation}</span> - </li> - {/each} - </ul> -{/if} diff --git a/src/pages/Index.svelte b/src/pages/Index.svelte @@ -1,19 +1,164 @@ <script> + import autocomplete from 'accessible-autocomplete' + import search from 'fuzzysearch' + import day from 'dayjs' + import utc from 'dayjs/plugin/utc' + import isOnline from 'is-online' + import { nanoid } from 'nanoid' + import { onMount } from 'svelte' import { isDarkMode } from '../stores' + import { pouch, replicateToCouch } from '../couch' + import DateTimeGrid from '../components/DateTimeGrid.svelte' + day.extend(utc) document.title = 'Koro' + let submitting = false + let success = false + let errorFlash = '' + let durationInput = null + let durationsList = [ + '15 minutes', + '30 minutes', + '45 minutes', + '1 hour', + '2 hours', + '3 hours', + '4 hours', + '5 hours' + ] + let event = { + _id: nanoid(), + name: '', + duration: '', + times: [] + } + let eventLink = '' + + async function submitForm() { + errorFlash = '' + submitting = true + if (!event.name) { + errorFlash = 'Please set a name for your event!' + submitting = false + return + } + + if (!event.duration) { + errorFlash = 'Please set a duration for your event!' + submitting = false + return + } + + if (!durationsList.includes(event.duration)) { + errorFlash = 'The duration must be picked from the provided list!' + submitting = false + return + } + + if (event.times.length === 0) { + errorFlash = 'Please pick at least one event time!' + submitting = false + return + } + + const db = await pouch() + try { + const res = await db.put({ + times: event.times.map((e) => day(e).utc().format()), + ...event + }) + await replicateToCouch() + success = true + eventLink = `https://koro.moe/${res.id}` + } catch (e) { + console.error(e) + } + } + + function durationSource(query, cb) { + if (durationsList.includes(query)) cb([]) + const filtered = durationsList.filter((d) => search(query, d)) + cb(filtered) + } + + onMount(async () => { + if (!(await isOnline())) { + errorFlash = `You're offline! This means you can still create an event, but you'll have to be online before you can share the link with others.` + } + autocomplete({ + element: document.querySelector('#duration-mount'), + id: 'duration', + source: durationSource, + confirmOnBlur: false, + onConfirm: (opt) => { + event.duration = opt + } + }) + }) </script> -<h1 class="text-5xl font-serif font-extrabold">Koro</h1> -<p class="text-2xl font-medium max-w-xl leading-normal"> - An event time planning site for the rest of us. You can plan event - times with as many people as you like, run polls, or simply display a bunch - of times in the user's timezone. It also (kind of) works when you're offline, - and it's free! -</p> - -<ul class="list-disc text-2xl mt-4"> - <li><a class="text-purple-400" href="/event">/event</a> - Flexibly schedule an event time with other people</li> - <li><a class="text-purple-400" href="/poll">/poll</a> - Create a poll</li> - <li><a class="text-purple-400" href="/times">/times</a> - Display times in the user's timezone</li> -</ul> +<h1 class="text-5xl font-serif font-extrabold">koro</h1> +{#if success} + <p class="text-2xl font-medium max-w-xl leading-normal"> + Your event has successfully been created! You can share the link below with + anyone you want to invite, and they can set their available times from the + ones you picked out. Don't forget to fill it out yourself! + </p> + + <div + class="text-2xl font-mono bg-black text-white max-w-xl w-full px-3 py-2 mt-5 + text-center"> + {eventLink} + </div> +{:else} + <p class="text-2xl font-medium max-w-xl leading-normal"> + An event time planning site for the rest of us. Use this to find a timeslot + for your next meeting, party, raid, whatever you want. It automatically + supports the user's timezone and works locally, too. + </p> + + <p class="text-2xl my-4 font-medium max-w-xl leading-normal"> + You can also create a + <a class="text-purple-400" href="/poll">regular poll</a>. + </p> + + <form class="max-w-xl mt-6" on:submit|preventDefault={submitForm}> + {#if errorFlash} + <div + class="w-full bg-red-100 px-3 py-2 border rounded border-red-400 + text-red-600 my-5"> + {errorFlash} + </div> + {/if} + <label + for="name" + class="font-bold text-gray-300" + class:text-gray-600={!$isDarkMode}> + Your event's name + </label> + <input + id="name" + type="text" + bind:value={event.name} + class:dark={$isDarkMode} + placeholder="Raid with the boys" /> + <label + for="duration" + class="font-bold text-gray-300 inline-block mt-4" + class:text-gray-600={!$isDarkMode}> + Duration of the event + </label> + <div id="duration-mount" class="mb-5" class:dark={$isDarkMode} /> + <label class="font-bold text-gray-300" class:text-gray-600={!$isDarkMode}> + Event start times + </label> + </form> + <DateTimeGrid startDate={new Date()} bind:selected={event.times} /> + <button + class="btn mt-5" + type="submit" + on:click|preventDefault={submitForm} + disabled={submitting}> + Create event + </button> +{/if}