This commit is contained in:
tree 2022-04-11 21:31:12 +02:00
rodič b3db1527b8
revize fe4bfb670b
16 změnil soubory, kde provedl 1321 přidání a 740 odebrání

40
package-lock.json vygenerováno
Zobrazit soubor

@ -22,6 +22,8 @@
"@sveltejs/kit": "next",
"autoprefixer": "^10.4.4",
"postcss": "^8.4.12",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.46.0",
"svelte-markdown": "^0.2.2",
"tailwindcss": "^3.0.23"
@ -1571,6 +1573,31 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"node_modules/prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-svelte": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.7.0.tgz",
"integrity": "sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==",
"dev": true,
"peerDependencies": {
"prettier": "^1.16.4 || ^2.0.0",
"svelte": "^3.2.0"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -2895,6 +2922,19 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true
},
"prettier-plugin-svelte": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-2.7.0.tgz",
"integrity": "sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==",
"dev": true,
"requires": {}
},
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",

Zobrazit soubor

@ -6,7 +6,9 @@
"build": "svelte-kit build",
"package": "svelte-kit package",
"preview": "svelte-kit preview",
"prepare": "svelte-kit sync"
"prepare": "svelte-kit sync",
"prettier": "prettier --write --plugin-search-dir=. ./**/*.svelte"
},
"devDependencies": {
"@faker-js/faker": "^6.1.2",
@ -15,6 +17,8 @@
"@sveltejs/kit": "next",
"autoprefixer": "^10.4.4",
"postcss": "^8.4.12",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"svelte": "^3.46.0",
"svelte-markdown": "^0.2.2",
"tailwindcss": "^3.0.23"

Zobrazit soubor

@ -1,112 +1,150 @@
<script>
export let speaker;
export let col = 'speakers';
export let size = 'normal';
export let col = "speakers";
export let size = "normal";
export let customSize = null;
import SvelteMarkdown from 'svelte-markdown';
import Link from '$lib/Link.svelte';
import { page } from '$app/stores';
import SvelteMarkdown from "svelte-markdown";
import Link from "$lib/Link.svelte";
import { page } from "$app/stores";
const renderers = { link: Link }
const renderers = { link: Link };
let imagesRoot = 'https://spec.utxo.cz/22/photos'
let imagesRoot = "https://spec.utxo.cz/22/photos";
/*if ($page.url.hostname === 'localhost') {
imagesRoot = 'http://localhost:8000/22/photos'
}*/
const priority = [ 'web:svg', 'web:webp', 'web:png', 'web:jpg', 'twitter:jpg' ]
const priority = ["web:svg", "web:webp", "web:png", "web:jpg", "twitter:jpg"];
$: photos = getPhotos(speaker)
$: speakerImg = photos[0]
$: speakerImgAlt = photos[1]
$: photos = getPhotos(speaker);
$: speakerImg = photos[0];
$: speakerImgAlt = photos[1];
function getPhotos (sp) {
const output = []
function getPhotos(sp) {
const output = [];
if (speaker.photos && speaker.photos.length > 0) {
for (const prio of priority) {
if (speaker.photos.includes(prio)) {
const [ ext, format ] = prio.split(':')
const fn = `${imagesRoot}/${col}/${speaker.id}-${ext}.${format}`
const [ext, format] = prio.split(":");
const fn = `${imagesRoot}/${col}/${speaker.id}-${ext}.${format}`;
if (output[0]) {
output.push(fn)
break
output.push(fn);
break;
}
output.push(fn)
output.push(fn);
}
}
}
return output
return output;
}
if (!speakerImg) {
speakerImg = '/img/twitter-avatar.png'
speakerImg = "/img/twitter-avatar.png";
}
function getFlagEmoji(countryCode) {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt());
.split("")
.map((char) => 127397 + char.charCodeAt());
return String.fromCodePoint(...codePoints);
}
$: country = speaker.country ? getFlagEmoji(speaker.country) : ''
$: currentImg = speakerImg
$: country = speaker.country ? getFlagEmoji(speaker.country) : "";
$: currentImg = speakerImg;
function mouseOver () {
function mouseOver() {
if (speakerImgAlt) {
$: currentImg = speakerImgAlt
$: currentImg = speakerImgAlt;
}
}
function mouseLeave () {
$: currentImg = speakerImg
function mouseLeave() {
$: currentImg = speakerImg;
}
</script>
{#if size === 'custom'}
<div class="customSize} text-center pb-4 m-auto">
<img src={currentImg} class="{customSize} rounded-full m-auto" alt={speaker.name} />
</div>
{#if size === "custom"}
<div class="customSize} text-center pb-4 m-auto">
<img
src={currentImg}
class="{customSize} rounded-full m-auto"
alt={speaker.name}
/>
</div>
{/if}
{#if size === 'big'}
<div class="w-64 text-center pb-4 m-auto">
<img src={currentImg} class="w-64 rounded-full m-auto shadow-xl" alt={speaker.name} on:mouseover={mouseOver} on:mouseleave={mouseLeave} />
</div>
{#if size === "big"}
<div class="w-64 text-center pb-4 m-auto">
<img
src={currentImg}
class="w-64 rounded-full m-auto shadow-xl"
alt={speaker.name}
on:mouseover={mouseOver}
on:mouseleave={mouseLeave}
/>
</div>
{/if}
{#if size === 'normal'}
<div class="w-36 sm:w-44 text-center pb-4">
<a href="/lide?id={speaker.id}" on:mouseover={mouseOver} on:mouseleave={mouseLeave}><img src={currentImg} class="w-36 sm:w-40 rounded-full m-auto shadow-xl" alt={speaker.name} /></a>
<div class="mt-4 text-sm text-blue-web uppercase font-bold"><a href="/lide?id={speaker.id}">{speaker.name}</a> {country}</div>
{#if size === "normal"}
<div class="w-36 sm:w-44 text-center pb-4">
<a
href="/lide?id={speaker.id}"
on:mouseover={mouseOver}
on:mouseleave={mouseLeave}
><img
src={currentImg}
class="w-36 sm:w-40 rounded-full m-auto shadow-xl"
alt={speaker.name}
/></a
>
<div class="mt-4 text-sm text-blue-web uppercase font-bold">
<a href="/lide?id={speaker.id}">{speaker.name}</a>
{country}
</div>
{#if speaker.bio || speaker.orgs}
<div class="mt-1 text-xs text-blue-web italic"><SvelteMarkdown source={speaker.bio || speaker.orgs} renderers={renderers}/></div>
<div class="mt-1 text-xs text-blue-web italic">
<SvelteMarkdown source={speaker.bio || speaker.orgs} {renderers} />
</div>
{/if}
</div>
</div>
{/if}
{#if size === 'small'}
<div class="w-16 text-center">
<img src={currentImg} class="w-16 rounded-full m-auto" alt="{speaker.name}" />
</div>
{#if size === "small"}
<div class="w-16 text-center">
<img src={currentImg} class="w-16 rounded-full m-auto" alt={speaker.name} />
</div>
{/if}
{#if size === 'semi-small'}
<div class="w-10 text-center">
<img src={currentImg} class="w-10 rounded-full m-auto shadow-md" alt="{speaker.name}" />
</div>
{#if size === "semi-small"}
<div class="w-10 text-center">
<img
src={currentImg}
class="w-10 rounded-full m-auto shadow-md"
alt={speaker.name}
/>
</div>
{/if}
{#if size === 'extra-small'}
<div class="w-6 h-6 text-center">
<a href="/lide?id={speaker.id}"><img src={currentImg} class="w-6 rounded-full m-auto" alt="{speaker.name}" /></a>
</div>
{#if size === "extra-small"}
<div class="w-6 h-6 text-center">
<a href="/lide?id={speaker.id}"
><img
src={currentImg}
class="w-6 rounded-full m-auto"
alt={speaker.name}
/></a
>
</div>
{/if}
{#if size === 'micro'}
<div class="w-4 h-4 text-center">
<a href="/lide?id={speaker.id}"><img src={currentImg} class="w-4 rounded-full m-auto" alt="{speaker.name}" /></a>
</div>
{#if size === "micro"}
<div class="w-4 h-4 text-center">
<a href="/lide?id={speaker.id}"
><img
src={currentImg}
class="w-4 rounded-full m-auto"
alt={speaker.name}
/></a
>
</div>
{/if}

Zobrazit soubor

@ -3,53 +3,70 @@
const e = event;
import Avatar from '$lib/Avatar.svelte';
import EventTypeLabel from '$lib/EventTypeLabel.svelte';
import Avatar from "$lib/Avatar.svelte";
import EventTypeLabel from "$lib/EventTypeLabel.svelte";
import { bundle, userData } from '$lib/stores.js';
import { bundle, userData } from "$lib/stores.js";
function speakersMap (arr) {
function speakersMap(arr) {
if (!arr) return;
return arr.map(sId => {
return $bundle.spec.speakers.find(sp => sp.id === sId)
})
return arr.map((sId) => {
return $bundle.spec.speakers.find((sp) => sp.id === sId);
});
}
function trackRender (trackId) {
const track = $bundle.spec.tracks.find(t => t.id === trackId)
return track.shortname || track.name
function trackRender(trackId) {
const track = $bundle.spec.tracks.find((t) => t.id === trackId);
return track.shortname || track.name;
}
function getParents (e) {
return $bundle.spec.events.filter(i => i.parent === e.id)
function getParents(e) {
return $bundle.spec.events.filter((i) => i.parent === e.id);
}
function handleFavorite (el) {
const t = el.target.getAttribute('utxo-event-id')
userData.update(data => {
const fe = data.favoriteEvents
let output = null
function handleFavorite(el) {
const t = el.target.getAttribute("utxo-event-id");
userData.update((data) => {
const fe = data.favoriteEvents;
let output = null;
if (fe.includes(t)) {
output = Object.assign($userData, { favoriteEvents: fe.filter(f => f !== t) } )
output = Object.assign($userData, {
favoriteEvents: fe.filter((f) => f !== t),
});
} else {
fe.push(t)
output = Object.assign($userData, { favoriteEvents: fe })
fe.push(t);
output = Object.assign($userData, { favoriteEvents: fe });
}
//localStorage.setItem('userData', JSON.stringify(output))
return output
})
return output;
});
}
</script>
<div class="transition-all mb-4 border px-3 py-2 rounded-md shadow {$userData.favoriteEvents.includes(e.id) ? 'bg-yellow-100' : '' }" >
<div class="float-right"><i class="fa-star {$userData.favoriteEvents.includes(e.id) ? 'fa-solid' : 'fa-regular'} cursor-pointer" utxo-event-id="{e.id}" on:click={handleFavorite}></i></div>
<div class="text-lg font-semibold"><a href="/udalosti?id={e.id}">{e.name}</a></div>
<div
class="transition-all mb-4 border px-3 py-2 rounded-md shadow {$userData.favoriteEvents.includes(
e.id
)
? 'bg-yellow-100'
: ''}"
>
<div class="float-right">
<i
class="fa-star {$userData.favoriteEvents.includes(e.id)
? 'fa-solid'
: 'fa-regular'} cursor-pointer"
utxo-event-id={e.id}
on:click={handleFavorite}
/>
</div>
<div class="text-lg font-semibold">
<a href="/udalosti?id={e.id}">{e.name}</a>
</div>
{#if e.speakers && e.speakers.length > 0}
<div class="mt-1 mb-2 flex flex-wrap gap-2">
{#each speakersMap(e.speakers) as s}
<div class="flex gap-1.5">
<Avatar speaker={s} size='extra-small' />
<Avatar speaker={s} size="extra-small" />
<div class="m-auto"><a href="/lide?id={s.id}">{s.name}</a></div>
</div>
{/each}
@ -65,13 +82,18 @@
<div class="flex flex-wrap gap-2" cellpadding="5">
{#each getParents(e) as pe}
<div class="border rounded py-1.5 px-2.5 bg-gray-100 text-sm">
<div class="font-bold"><a href="/udalosti?id={pe.id}">{pe.name}</a></div>
<div class="font-bold">
<a href="/udalosti?id={pe.id}">{pe.name}</a>
</div>
<div class="mt-1">
{#if pe.speakers.length === 0}
<div>TBA</div>
{:else}
{#each speakersMap(pe.speakers) as s}
<div class="flex gap-1"><Avatar speaker={s} size='micro' /><div><a href="/lide?id={s.id}">{s.name}</a></div></div>
<div class="flex gap-1">
<Avatar speaker={s} size="micro" />
<div><a href="/lide?id={s.id}">{s.name}</a></div>
</div>
{/each}
{/if}
</div>

Zobrazit soubor

@ -3,19 +3,23 @@
export let size = null;
const config = {
panel: { title: 'Panelová debata', style: 'bg-custom-red text-white' },
talk: { title: 'Přednáška', style: 'bg-custom-green text-white' },
workshop: { title: 'Workshop', style: 'bg-custom-blue text-white' },
other: { title: 'Ostatní', style: 'bg-custom-yellow' },
lightning: { title: 'Lightning talk', style: 'bg-pink-400' },
}
panel: { title: "Panelová debata", style: "bg-custom-red text-white" },
talk: { title: "Přednáška", style: "bg-custom-green text-white" },
workshop: { title: "Workshop", style: "bg-custom-blue text-white" },
other: { title: "Ostatní", style: "bg-custom-yellow" },
lightning: { title: "Lightning talk", style: "bg-pink-400" },
};
const current = config[event.type];
</script>
<div class="flex { size === 'big' ? 'h-6 text-sm' : 'h-5 text-xs' }">
<div class="w-1 rounded-l-sm {current.style}"></div>
<div class="{ size === 'big' ? 'px-2 py-0.5' : 'px-1.5 py-0.5' } rounded-r-sm bg-gray-100 uppercase">{current.title}</div>
<div class="flex {size === 'big' ? 'h-6 text-sm' : 'h-5 text-xs'}">
<div class="w-1 rounded-l-sm {current.style}" />
<div
class="{size === 'big'
? 'px-2 py-0.5'
: 'px-1.5 py-0.5'} rounded-r-sm bg-gray-100 uppercase"
>
{current.title}
</div>
</div>

Zobrazit soubor

@ -1,14 +1,18 @@
<script>
import { bundle } from '$lib/stores';
import SocialButtons from '$lib/SocialButtons.svelte';
import { bundle } from "$lib/stores";
import SocialButtons from "$lib/SocialButtons.svelte";
</script>
{#if $bundle}
<div class="bg-blue-web-bg text-white">
<div class="relative mx-auto px-6 pt-10 pb-2 sm:pb-6 max-w-6xl sm:flex pr-4">
<div class="bg-blue-web-bg text-white">
<div
class="relative mx-auto px-6 pt-10 pb-2 sm:pb-6 max-w-6xl sm:flex pr-4"
>
<div class="flex-1">
<div>
<a href="/"><img src="/img/logo-white.svg" alt="UTXO.22" class="w-24" /></a>
<a href="/"
><img src="/img/logo-white.svg" alt="UTXO.22" class="w-24" /></a
>
</div>
<div class="mt-4 font-semibold">
4.-5. červen 2022 @ Gabriel Loci, Praha
@ -17,22 +21,43 @@
Otevřená komunitní kryptoměnová konference
</div>
<div class="mt-4">
<a href="{$bundle.links.docs}" class="hover:text-red-500"><i class="fas fa-book" />&nbsp; Dokumentace</a>
<a href={$bundle.links.docs} class="hover:text-red-500"
><i class="fas fa-book" />&nbsp; Dokumentace</a
>
</div>
</div>
<div class="sm:mt-0 mt-6 sm:w-1/3 mr-2">
<SocialButtons size="normal" />
</div>
</div>
<div class="relative mx-auto px-6 pt-10 pb-6 max-w-6xl text-xs opacity-50 sm:flex">
<div
class="relative mx-auto px-6 pt-10 pb-6 max-w-6xl text-xs opacity-50 sm:flex"
>
<div class="flex-1 mt-2 mb-2">
<i class="fas fa-heart text-red-500" /> S láskou organizuje <a href="https://utxo.foundation" class="underline hover:no-underline" target="_blank">UTXO Foundation, z.s.</a>
<i class="fas fa-heart text-red-500" /> S láskou organizuje
<a
href="https://utxo.foundation"
class="underline hover:no-underline"
target="_blank">UTXO Foundation, z.s.</a
>
</div>
<div>
<a href="https://github.com/utxo-foundation/utxo22-web" target="_blank"><span class="font-bold">v0.9.1</span></a> | powered by
<a href="https://svelte.dev/" class="font-bold" target="_blank" ><img src="/img/svelte-logo.svg" class="w-5 inline" alt="Svelte" /> Svelte</a> |
grafický návrh <a href="https://www.ppmedia.cz/" target="_blank"><img src="/img/pen-production-logo.svg" class="w-28 inline-block pb-2 ml-1" alt="Pen&Production" /></a>
<a href="https://github.com/utxo-foundation/utxo22-web" target="_blank"
><span class="font-bold">v0.9.1</span></a
>
| powered by
<a href="https://svelte.dev/" class="font-bold" target="_blank"
><img src="/img/svelte-logo.svg" class="w-5 inline" alt="Svelte" /> Svelte</a
>
| grafický návrh
<a href="https://www.ppmedia.cz/" target="_blank"
><img
src="/img/pen-production-logo.svg"
class="w-28 inline-block pb-2 ml-1"
alt="Pen&Production"
/></a
>
</div>
</div>
</div>
</div>
{/if}

Zobrazit soubor

@ -1,6 +1,8 @@
<script>
export let href = ''
export let title = undefined
export let href = "";
export let title = undefined;
</script>
<a {href} {title} class="underline hover:no-underline" target="_blank"><slot></slot></a>
<a {href} {title} class="underline hover:no-underline" target="_blank"
><slot /></a
>

Zobrazit soubor

@ -1,34 +1,42 @@
<script>
export let size = 'small';
export let size = "small";
import { bundle } from '$lib/stores';
import { page } from '$app/stores';
import { bundle } from "$lib/stores";
import { page } from "$app/stores";
const socials = [
{ link: 'twitter', ico: 'fa-brands fa-twitter', name: 'Twitter' },
{ link: 'instagram', ico: 'fab fa-instagram', name: 'Instagram' },
{ link: 'fbevent', ico: 'fab fa-facebook', name: 'Facebook' },
{ link: 'substack', ico: 'fa-solid fa-envelope', name: 'Newsletter' },
{ link: 'discord', ico: 'fab fa-discord', name: 'Discord' },
{ link: 'telegram', ico: 'fab fa-telegram', name: 'Telegram' },
]
{ link: "twitter", ico: "fa-brands fa-twitter", name: "Twitter" },
{ link: "instagram", ico: "fab fa-instagram", name: "Instagram" },
{ link: "fbevent", ico: "fab fa-facebook", name: "Facebook" },
{ link: "substack", ico: "fa-solid fa-envelope", name: "Newsletter" },
{ link: "discord", ico: "fab fa-discord", name: "Discord" },
{ link: "telegram", ico: "fab fa-telegram", name: "Telegram" },
];
</script>
{#if $bundle}
{#if size === 'small'}
{#if size === "small"}
<div class="flex block space-x-2 m-auto w-full justify-end">
{#each socials as soc}
<a href="{$bundle.links[soc.link]}" class="w-6 h-6 bg-white rounded-full hover:bg-utxo-gradient hover:text-white" target="_blank">
<i class="{soc.ico}" />
<a
href={$bundle.links[soc.link]}
class="w-6 h-6 bg-white rounded-full hover:bg-utxo-gradient hover:text-white"
target="_blank"
>
<i class={soc.ico} />
</a>
{/each}
</div>
{/if}
{#if size === 'normal'}
{#if size === "normal"}
<div class="w-auto">
<div class="sm:flex flex-wrap gap-3 justify-end">
{#each socials as soc}
<div class="mr-4 sm:mr-0 inline-block sm:block hover:text-red-500"><a href="{$bundle.links[soc.link]}" class="block flex" target="_blank"><i class="{soc.ico} mr-2 my-auto" />{soc.name}</a></div>
<div class="mr-4 sm:mr-0 inline-block sm:block hover:text-red-500">
<a href={$bundle.links[soc.link]} class="block flex" target="_blank"
><i class="{soc.ico} mr-2 my-auto" />{soc.name}</a
>
</div>
{/each}
</div>
</div>

Zobrazit soubor

@ -1,31 +1,61 @@
<script>
import { page } from '$app/stores';
import { bundle, userData, userDataLocal } from '$lib/stores';
import SocialButtons from '$lib/SocialButtons.svelte';
import { page } from "$app/stores";
import { bundle, userData, userDataLocal } from "$lib/stores";
import SocialButtons from "$lib/SocialButtons.svelte";
function logoClick () {
userData.update(ud => {
ud.hpTrack = 'top'
return ud
})
function logoClick() {
userData.update((ud) => {
ud.hpTrack = "top";
return ud;
});
}
</script>
<header class="relative" style="background-color: #32375C;">
<!-- <li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li> -->
<nav class="relative mx-auto lg:px-6 px-4 pt-4 sm:pt-6 sm:pb-6 pb-2 max-w-6xl text-center">
<nav
class="relative mx-auto lg:px-6 px-4 pt-4 sm:pt-6 sm:pb-6 pb-2 max-w-6xl text-center"
>
<div class="">
<div class="lg:flex lg:flex-wrap lg:space-x-10">
<div class="block justify-start lg:flex-1 my-auto text-center pb-3 lg:pb-0">
<div class="w-36 lg:w-24 inline-block lg:block"><a href="/" on:click={logoClick}><img src="/img/logo-white.svg" class="w-full" alt="UTXO.22" /></a></div>
<div
class="block justify-start lg:flex-1 my-auto text-center pb-3 lg:pb-0"
>
<div class="w-36 lg:w-24 inline-block lg:block">
<a href="/" on:click={logoClick}
><img src="/img/logo-white.svg" class="w-full" alt="UTXO.22" /></a
>
</div>
</div>
<div class="flex lg:space-x-10 uppercase text-sm font-bold text-white">
<a sveltekit:prefetch href="/" class="lg:w-auto w-1/3 m-auto hover:text-[#E16A61]" class:text-blue-400={$page.url.pathname === '/'}>O konferenci</a>
<a sveltekit:prefetch href="/program" class="lg:w-auto w-1/3 m-auto hover:text-[#E16A61]" class:text-blue-400={$page.url.pathname === '/program'}>Program</a>
<a sveltekit:prefetch href="/vstupenky" class="lg:w-auto w-1/3 m-auto border-solid border border-[#E16A61] rounded-full {$page.url.pathname === '/vstupenky' ? 'border-0 bg-utxo-gradient m-px' : 'hover:border-0 hover:bg-utxo-gradient hover:p-px' }"><div class="py-2 px-1 lg:px-8">Vstupenky{#if $userDataLocal.tickets && $userDataLocal.tickets.length > 0}&nbsp;({$userDataLocal.tickets.length}){/if}</div></a>
<a
sveltekit:prefetch
href="/"
class="lg:w-auto w-1/3 m-auto hover:text-[#E16A61]"
class:text-blue-400={$page.url.pathname === "/"}>O konferenci</a
>
<a
sveltekit:prefetch
href="/program"
class="lg:w-auto w-1/3 m-auto hover:text-[#E16A61]"
class:text-blue-400={$page.url.pathname === "/program"}>Program</a
>
<a
sveltekit:prefetch
href="/vstupenky"
class="lg:w-auto w-1/3 m-auto border-solid border border-[#E16A61] rounded-full {$page
.url.pathname === '/vstupenky'
? 'border-0 bg-utxo-gradient m-px'
: 'hover:border-0 hover:bg-utxo-gradient hover:p-px'}"
><div class="py-2 px-1 lg:px-8">
Vstupenky{#if $userDataLocal.tickets && $userDataLocal.tickets.length > 0}&nbsp;({$userDataLocal
.tickets.length}){/if}
</div></a
>
</div>
<div class="hidden lg:block my-auto lg:flex-1 lg:pt-0 pt-4 lg:justify-end justify-center">
<div
class="hidden lg:block my-auto lg:flex-1 lg:pt-0 pt-4 lg:justify-end justify-center"
>
<SocialButtons />
</div>
</div>

Zobrazit soubor

@ -1,44 +1,41 @@
<script>
import Header from '$lib/header/Header.svelte';
import Footer from '$lib/Footer.svelte';
import '../app.css';
import api from '$lib/api.js';
import { page } from '$app/stores';
import { userData, userDataLocal, apiStatus } from '$lib/stores';
import { loadOrders, loadApiStatus } from '$lib/orders';
import { onMount, onDestroy } from 'svelte';
import Header from "$lib/header/Header.svelte";
import Footer from "$lib/Footer.svelte";
import "../app.css";
import api from "$lib/api.js";
import { page } from "$app/stores";
import { userData, userDataLocal, apiStatus } from "$lib/stores";
import { loadOrders, loadApiStatus } from "$lib/orders";
import { onMount, onDestroy } from "svelte";
let bundle = null
let uds = null
let bundle = null;
let uds = null;
onMount(async () => {
bundle = await api.loadBundle($page.url.hostname === 'localhost')
bundle = await api.loadBundle($page.url.hostname === "localhost");
const userDataLS = localStorage.getItem('userData')
const userDataLS = localStorage.getItem("userData");
if (userDataLS) {
userData.set(JSON.parse(userDataLS))
userData.set(JSON.parse(userDataLS));
}
uds = userData.subscribe(ud => {
localStorage.setItem('userData', JSON.stringify(ud))
})
uds = userData.subscribe((ud) => {
localStorage.setItem("userData", JSON.stringify(ud));
});
await loadApiStatus()
await loadOrders($userData)
})
await loadApiStatus();
await loadOrders($userData);
});
onDestroy(() => {
//userData.unsubscribe(uds)
})
});
// load orders
</script>
{#if bundle}
<div class="layout min-h-screen bg-gray-900">
<div class="layout min-h-screen bg-gray-900">
<div class="inset-0 bg-white">
<Header />
@ -47,7 +44,7 @@
</main>
</div>
<Footer />
</div>
</div>
{/if}
<style>

Zobrazit soubor

@ -7,11 +7,41 @@
<section class="relative mx-auto py-6 sm:py-10 px-6 max-w-6xl text-blue-web">
<div class="">
<div class="text-3xl lg:text-4xl text-center">🎉 Gratulujeme k nákupu vstupenek!</div>
<div class="mt-4 text-lg text-center">Najdeš je v mailu nebo na stránce <a href="/vstupenky" class="underline hover:no-underline">vstupenky</a></div>
<div class="flex mt-6"><img class="m-auto w-auto lg:max-w-3xl" src="/gifs/funny-celebrate-{Math.round((Math.random() * (9 - 1) + 1))}.gif" /></div>
<div class="mt-10 text-xl text-center mt-6">Co teď? Poděl se o tu radost s ostatními <a href="https://twitter.com/intent/tweet?text=M%C3%A1m%20l%C3%ADstek%20na%20%40utxoprague%20%F0%9F%8E%89%20Poj%C4%8F%20taky%21%0A%20%23utxo22" class="underline hover:no-underline" target="_blank">tweetem</a></div>
<div class="text-xl text-center mt-6">Prohlédni si <a href="/program" class="underline hover:no-underline">aktuální program</a></div>
<div class="text-xl text-center mt-6">Sleduj novinky na <a href="https://twitter.com/utxoprague" class="underline hover:no-underline" target="_blank">Twitteru</a>&nbsp;<i class="fa-brands fa-twitter"></i></div>
<div class="text-3xl lg:text-4xl text-center">
🎉 Gratulujeme k nákupu vstupenek!
</div>
<div class="mt-4 text-lg text-center">
Najdeš je v mailu nebo na stránce <a
href="/vstupenky"
class="underline hover:no-underline">vstupenky</a
>
</div>
<div class="flex mt-6">
<img
class="m-auto w-auto lg:max-w-3xl"
src="/gifs/funny-celebrate-{Math.round(
Math.random() * (9 - 1) + 1
)}.gif"
/>
</div>
<div class="mt-10 text-xl text-center mt-6">
Co teď? Poděl se o tu radost s ostatními <a
href="https://twitter.com/intent/tweet?text=M%C3%A1m%20l%C3%ADstek%20na%20%40utxoprague%20%F0%9F%8E%89%20Poj%C4%8F%20taky%21%0A%20%23utxo22"
class="underline hover:no-underline"
target="_blank">tweetem</a
>
</div>
<div class="text-xl text-center mt-6">
Prohlédni si <a href="/program" class="underline hover:no-underline"
>aktuální program</a
>
</div>
<div class="text-xl text-center mt-6">
Sleduj novinky na <a
href="https://twitter.com/utxoprague"
class="underline hover:no-underline"
target="_blank">Twitteru</a
>&nbsp;<i class="fa-brands fa-twitter" />
</div>
</div>
</section>

Zobrazit soubor

@ -3,59 +3,92 @@
</script>
<script>
import { bundle, userData } from '$lib/stores.js';
import Avatar from '$lib/Avatar.svelte';
import SvelteMarkdown from 'svelte-markdown';
import Link from '$lib/Link.svelte';
const renderers = { link: Link }
import { bundle, userData } from "$lib/stores.js";
import Avatar from "$lib/Avatar.svelte";
import SvelteMarkdown from "svelte-markdown";
import Link from "$lib/Link.svelte";
const renderers = { link: Link };
let onlyLead = true
let onlyLeadPreview = false
let onlyLead = true;
let onlyLeadPreview = false;
$: currentBundle = $bundle;
$: leadSpeakersCount = currentBundle ? currentBundle.spec.speakers.filter(s => !!s.lead).length : 0
$: tracks = currentBundle ? [{ name: 'Hlavní přednášející ('+leadSpeakersCount+')', id: 'top' }, { name: 'Vše', id: null }].concat(currentBundle.spec.tracks) : null
$: leadSpeakersCount = currentBundle
? currentBundle.spec.speakers.filter((s) => !!s.lead).length
: 0;
$: tracks = currentBundle
? [
{ name: "Hlavní přednášející (" + leadSpeakersCount + ")", id: "top" },
{ name: "Vše", id: null },
].concat(currentBundle.spec.tracks)
: null;
function changeTrack (tId) {
function changeTrack(tId) {
return function () {
userData.update(ud => { ud.hpTrack = tId; return ud; })
onlyLead = !tId
}
userData.update((ud) => {
ud.hpTrack = tId;
return ud;
});
onlyLead = !tId;
};
}
function handleShowFull () {
userData.update(ud => { ud.hpTrack = null; return ud })
function handleShowFull() {
userData.update((ud) => {
ud.hpTrack = null;
return ud;
});
}
</script>
<svelte:head>
<title>UTXO.22 - 4-5. červen 2022 {$bundle ? '- '+$bundle.description : ''}</title>
<title
>UTXO.22 - 4-5. červen 2022 {$bundle
? "- " + $bundle.description
: ""}</title
>
</svelte:head>
<section class="relative mx-auto py-6 sm:py-10 px-6 max-w-6xl">
{#if $bundle}
<div class="flex flex-wrap gap-1.5 sm:gap-3 text-xs uppercase font-bold text-blue-web justify-left">
<div
class="flex flex-wrap gap-1.5 sm:gap-3 text-xs uppercase font-bold text-blue-web justify-left"
>
{#each tracks as track}
<div class="py-1.5 sm:py-2 px-2.5 sm:px-8 rounded-full shadow border border-solid {$userData.hpTrack === track.id ? 'bg-utxo-gradient border-0 text-white' : 'border-blue-web hover:bg-blue-web hover:text-white hover:border-transparent cursor-pointer'}" on:click={changeTrack(track.id)}>{track.shortname || track.name} {#if !track.id}({$bundle.spec.speakers.length}){/if}</div>
<div
class="py-1.5 sm:py-2 px-2.5 sm:px-8 rounded-full shadow border border-solid {$userData.hpTrack ===
track.id
? 'bg-utxo-gradient border-0 text-white'
: 'border-blue-web hover:bg-blue-web hover:text-white hover:border-transparent cursor-pointer'}"
on:click={changeTrack(track.id)}
>
{track.shortname || track.name}
{#if !track.id}({$bundle.spec.speakers.length}){/if}
</div>
{/each}
</div>
<div class="flex flex-wrap gap-6 mt-6 sm:mt-14 justify-center">
{#each $bundle.spec.speakers as speaker}
{#if ($userData.hpTrack === 'top' && speaker.lead === true) || $userData.hpTrack !== 'top'}
{#if (!$userData.hpTrack || speaker.tracks.includes($userData.hpTrack)) || $userData.hpTrack === 'top'}
<Avatar speaker={speaker} />
{#if ($userData.hpTrack === "top" && speaker.lead === true) || $userData.hpTrack !== "top"}
{#if !$userData.hpTrack || speaker.tracks.includes($userData.hpTrack) || $userData.hpTrack === "top"}
<Avatar {speaker} />
{/if}
{/if}
{/each}
</div>
{#if $userData.hpTrack === 'top'}
{#if $userData.hpTrack === "top"}
<div class="relative cursor-pointer mb-10">
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-white flex" on:click={handleShowFull}></div>
<div
class="absolute inset-0 bg-gradient-to-b from-transparent to-white flex"
on:click={handleShowFull}
/>
<div class="flex flex-wrap gap-3 mt-10 justify-center">
{#each $bundle.spec.speakers.filter(s => !s.lead).sort(() => .5 - Math.random()).slice(0,27) as speaker}
<Avatar speaker={speaker} size="small" />
{#each $bundle.spec.speakers
.filter((s) => !s.lead)
.sort(() => 0.5 - Math.random())
.slice(0, 27) as speaker}
<Avatar {speaker} size="small" />
{/each}
</div>
</div>
@ -67,24 +100,57 @@
<div class="relative mx-auto py-6 px-6 max-w-6xl">
<div class="py-6 md:py-10 md:flex gap-12">
<div class="block flex-1">
<img src="/photos/gabriel-loci.jpeg" class="flex rounded-xl shadow-xl" alt="Gabriel Loci" />
<img
src="/photos/gabriel-loci.jpeg"
class="flex rounded-xl shadow-xl"
alt="Gabriel Loci"
/>
<div class="flex mt-3 gap-3">
<div class="w-1/2"><img src="/photos/rajska-zahrada.jpeg" class="rounded-lg shadow-lg" alt="Gabriel Loci - Rajská zahrada" /></div>
<div class="w-1/2"><img src="/photos/knihovna.jpeg" class="rounded-lg shadow-lg" alt="Gabriel Loci - Knihovna" /></div>
<div class="w-1/2">
<img
src="/photos/rajska-zahrada.jpeg"
class="rounded-lg shadow-lg"
alt="Gabriel Loci - Rajská zahrada"
/>
</div>
<div class="w-1/2">
<img
src="/photos/knihovna.jpeg"
class="rounded-lg shadow-lg"
alt="Gabriel Loci - Knihovna"
/>
</div>
</div>
</div>
<div class="text-white md:w-1/2 md:pt-0 pt-6">
<div class="uppercase">Místo</div>
<div class="uppercase mt-3 text-4xl font-bold">Gabriel Loci</div>
<div class="mt-4 font-bold">
Holečkova 106/10, 150 00 Praha 5 - Smíchov 🇨🇿<br/><span class="font-normal"><a href="https://goo.gl/maps/u1aY4RxXMgcm889V7" class="underline hover:no-underline" target="_blank">Google Maps</a>, <a href="https://mapy.cz/s/cuvetubafo" class="underline hover:no-underline" target="_blank">Mapy.cz</a></span>
Holečkova 106/10, 150 00 Praha 5 - Smíchov 🇨🇿<br /><span
class="font-normal"
><a
href="https://goo.gl/maps/u1aY4RxXMgcm889V7"
class="underline hover:no-underline"
target="_blank">Google Maps</a
>,
<a
href="https://mapy.cz/s/cuvetubafo"
class="underline hover:no-underline"
target="_blank">Mapy.cz</a
></span
>
</div>
<div class="mt-4">
Benediktinky. Kulturní památka. Česká Pošta. Poštovní muzeum.
Na první pohled nesouvisející názvy, které jsou ale neodmyslitelnou součástí unikátního komplexu Gabriel Loci na pražském Smíchově doslova pár minut od centra.
Na konci 19.století v klášteře sídlily řeholnice, ženské opatství řádu benediktinek beuronské kongregace. Později prostory spravovalo československé Ministerstvo pošt a telegrafů.
Dnes komplex využívají hlavně natáčecí studia jako HBO, Netflix nebo i ČT. No a my! 💪<br/><br/>
Zažijte mysteriózní atmosféru komplexu na 1.ročníku konference UTXO.22. Část after-party si užijete doslova v pitevně ze seriálu Devadesátky 👌😀
Benediktinky. Kulturní památka. Česká Pošta. Poštovní muzeum. Na první
pohled nesouvisející názvy, které jsou ale neodmyslitelnou součástí
unikátního komplexu Gabriel Loci na pražském Smíchově doslova pár
minut od centra. Na konci 19.století v klášteře sídlily řeholnice,
ženské opatství řádu benediktinek beuronské kongregace. Později
prostory spravovalo československé Ministerstvo pošt a telegrafů. Dnes
komplex využívají hlavně natáčecí studia jako HBO, Netflix nebo i ČT.
No a my! 💪<br /><br />
Zažijte mysteriózní atmosféru komplexu na 1.ročníku konference UTXO.22.
Část after-party si užijete doslova v pitevně ze seriálu Devadesátky 👌😀
</div>
</div>
</div>
@ -96,42 +162,81 @@ Zažijte mysteriózní atmosféru komplexu na 1.ročníku konference UTXO.22. Č
<div class="text-2xl uppercase font-bold">Partneři</div>
<div class="mt-6">Sponzoři</div>
<div class="mt-6 flex flex-wrap gap-8 justify-left">
{#each $bundle.spec.partners.filter(p => p.type === 'sponsor') as p}
{#each $bundle.spec.partners.filter((p) => p.type === "sponsor") as p}
<div class="w-28">
<a href={p.web.url} target="_blank"><Avatar speaker={p} col="partners" size="custom" customSize="w-24 shadow-xl" /></a>
<a href={p.web.url} target="_blank"
><Avatar
speaker={p}
col="partners"
size="custom"
customSize="w-24 shadow-xl"
/></a
>
<div class="text-center text-sm uppercase font-bold">{p.name}</div>
</div>
{/each}
</div>
<div class="mt-10">Komunity</div>
<div class="mt-6 flex flex-wrap gap-6 justify-left">
{#each $bundle.spec.partners.filter(p => p.type === 'community') as p}
<div><a href={p.web ? p.web.url : (p.twitter ? `https://twitter.com/${p.twitter}` : '')} target="_blank"><Avatar speaker={p} col="partners" size="custom" customSize="w-20 shadow-lg" /></a></div>
{#each $bundle.spec.partners.filter((p) => p.type === "community") as p}
<div>
<a
href={p.web
? p.web.url
: p.twitter
? `https://twitter.com/${p.twitter}`
: ""}
target="_blank"
><Avatar
speaker={p}
col="partners"
size="custom"
customSize="w-20 shadow-lg"
/></a
>
</div>
{/each}
</div>
<div class="mt-10">Mediální partneři</div>
<div class="mt-6 flex flex-wrap gap-4 justify-left">
{#each $bundle.spec.partners.filter(p => p.type === 'medium') as p}
<div><a href={p.web ? p.web.url : (p.twitter ? `https://twitter.com/${p.twitter}` : '')} target="_blank"><Avatar speaker={p} col="partners" size="custom" customSize="w-16 shadow-lg" /></a></div>
{#each $bundle.spec.partners.filter((p) => p.type === "medium") as p}
<div>
<a
href={p.web
? p.web.url
: p.twitter
? `https://twitter.com/${p.twitter}`
: ""}
target="_blank"
><Avatar
speaker={p}
col="partners"
size="custom"
customSize="w-16 shadow-lg"
/></a
>
</div>
{/each}
</div>
</div>
</section>
{#if $bundle}
<section class="relative mx-auto py-10 px-6 max-w-6xl">
<section class="relative mx-auto py-10 px-6 max-w-6xl">
<div class="text-blue-web">
<h2 class="uppercase pt-5" id="faq">Často kladené dotazy (FAQ)</h2>
<div class="md:columns-2 columns-1 mt-8 h-auto">
{#each $bundle.spec.faqs as item}
<div class="mb-5 break-inside-avoid-column bg-blue-100/60 rounded-xl px-8 py-6 text-left transition-all box-shadow-light overflow-visible">
<div
class="mb-5 break-inside-avoid-column bg-blue-100/60 rounded-xl px-8 py-6 text-left transition-all box-shadow-light overflow-visible"
>
<div class="mb-4 font-bold">{item.question}</div>
<SvelteMarkdown source={item.answer} renderers={renderers} />
<SvelteMarkdown source={item.answer} {renderers} />
</div>
{/each}
</div>
</div>
</section>
</section>
{/if}
<style>

Zobrazit soubor

@ -3,47 +3,49 @@
</script>
<script>
import { goto } from "$app/navigation";
import Avatar from "$lib/Avatar.svelte";
import Event from "$lib/Event.svelte";
import { onMount, beforeUpdate } from "svelte";
import { page } from "$app/stores";
import { bundle } from "$lib/stores.js";
import SvelteMarkdown from "svelte-markdown";
import Link from "$lib/Link.svelte";
const renderers = { link: Link };
import { goto } from '$app/navigation';
import Avatar from '$lib/Avatar.svelte';
import Event from '$lib/Event.svelte';
import { onMount, beforeUpdate } from 'svelte';
import { page } from '$app/stores';
import { bundle } from '$lib/stores.js';
import SvelteMarkdown from 'svelte-markdown';
import Link from '$lib/Link.svelte';
const renderers = { link: Link }
$: id = getId($page.url.search);
$: s = $bundle ? $bundle.spec.speakers.find((s) => s.id === id) : null;
$: events = s
? $bundle.spec.events.filter(
(ev) => ev.speakers && ev.speakers.includes(s.id)
)
: [];
$: id = getId($page.url.search)
$: s = $bundle ? $bundle.spec.speakers.find(s => s.id === id) : null
$: events = s ? $bundle.spec.events.filter(ev => ev.speakers && ev.speakers.includes(s.id)) : []
function getId (search) {
const searchParams = new URLSearchParams(search)
const cid = searchParams.get('id')
if (!$bundle.spec.speakers.find(s => s.id === cid)) {
goto('/')
function getId(search) {
const searchParams = new URLSearchParams(search);
const cid = searchParams.get("id");
if (!$bundle.spec.speakers.find((s) => s.id === cid)) {
goto("/");
}
return cid
return cid;
}
function trackRender (trackId) {
const track = $bundle.spec.tracks.find(t => t.id === trackId)
return track.shortname || track.name
function trackRender(trackId) {
const track = $bundle.spec.tracks.find((t) => t.id === trackId);
return track.shortname || track.name;
}
function getFlagEmoji(countryCode) {
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt());
.split("")
.map((char) => 127397 + char.charCodeAt());
return String.fromCodePoint(...codePoints);
}
</script>
<svelte:head>
<title>{s ? s.name : ''} | Lidé | {$bundle ? $bundle.name : 'UTXO.22'}</title>
<title>{s ? s.name : ""} | Lidé | {$bundle ? $bundle.name : "UTXO.22"}</title>
</svelte:head>
<section class="relative mx-auto py-6 sm:py-10 px-6 max-w-6xl text-blue-web">
@ -54,33 +56,58 @@
<div class="mb-4 text-md uppercase">Přednášející</div>
<h1 class="text-2xl font-bold">{s.name} {getFlagEmoji(s.country)}</h1>
{#if s.nickname}
<div class="mt-1"><span class="text-xs">aka</span> <span class="font-bold">{s.nickname}</span></div>
<div class="mt-1">
<span class="text-xs">aka</span>
<span class="font-bold">{s.nickname}</span>
</div>
{/if}
{#if s.bio}
<div class="mt-4 text-blue-web italic"><SvelteMarkdown source={s.bio} renderers={renderers} /></div>
<div class="mt-4 text-blue-web italic">
<SvelteMarkdown source={s.bio} {renderers} />
</div>
{/if}
{#if s.orgs}
<div class="mt-4 text-blue-web links"><SvelteMarkdown source={s.orgs} renderers={renderers} /></div>
<div class="mt-4 text-blue-web links">
<SvelteMarkdown source={s.orgs} {renderers} />
</div>
{/if}
<div class="mt-4">Sekce: {s.tracks.map(t => trackRender(t)).join(', ')}</div>
<div class="mt-4">
Sekce: {s.tracks.map((t) => trackRender(t)).join(", ")}
</div>
{#if s.twitter}
<div class="mt-2">Twitter: <a href="https://twitter.com/{s.twitter}" target="_blank" class="font-bold">@{s.twitter}</a></div>
<div class="mt-2">
Twitter: <a
href="https://twitter.com/{s.twitter}"
target="_blank"
class="font-bold">@{s.twitter}</a
>
</div>
{/if}
{#if s.linkedin}
<div class="mt-2">LinkedIn: <a href="https://linkedin.com/in/{s.twitter}" target="_blank" class="font-bold">@{s.linkedin}</a></div>
<div class="mt-2">
LinkedIn: <a
href="https://linkedin.com/in/{s.twitter}"
target="_blank"
class="font-bold">@{s.linkedin}</a
>
</div>
{/if}
{#if s.web && s.web.url}
<div class="mt-2">Web: <a href="{s.web.url}" target="_blank" class="font-bold">{s.web.name || s.web.url.replace(/^https?:\/\//, '')}</a></div>
<div class="mt-2">
Web: <a href={s.web.url} target="_blank" class="font-bold"
>{s.web.name || s.web.url.replace(/^https?:\/\//, "")}</a
>
</div>
{/if}
</div>
</div>
{#if s.desc}
<div class="mt-6">
<SvelteMarkdown source={s.desc} renderers={renderers} />
<SvelteMarkdown source={s.desc} {renderers} />
</div>
{/if}
<div class="mt-6">
<h2 class="uppercase mb-4 text-md">Události ({ events.length })</h2>
<h2 class="uppercase mb-4 text-md">Události ({events.length})</h2>
<div>
{#if events.length > 0}
{#each events as e}

Zobrazit soubor

@ -3,22 +3,23 @@
</script>
<script>
import Event from '$lib/Event.svelte';
import { bundle, userData } from '$lib/stores.js';
import Event from "$lib/Event.svelte";
import { bundle, userData } from "$lib/stores.js";
</script>
<svelte:head>
<title>Program | UTXO.22</title>
</svelte:head>
<section class="relative mx-auto py-6 sm:py-10 px-6 max-w-6xl text-blue-web">
<h1 class="uppercase text-2xl font-bold">Program</h1>
<div class="mt-2 text-sm">Program stále připravujeme. Jeho konečná podoba bude zveřejněna pár týdnů před konferencí.</div>
<div class="mt-2 text-sm">
Program stále připravujeme. Jeho konečná podoba bude zveřejněna pár týdnů
před konferencí.
</div>
<div class="mt-6">
{#each $bundle.spec.events.filter(e => !e.parent) as e}
{#each $bundle.spec.events.filter((e) => !e.parent) as e}
<Event event={e} />
{/each}
</div>

Zobrazit soubor

@ -3,45 +3,46 @@
</script>
<script>
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
import { bundle, userData } from '$lib/stores.js';
import EventTypeLabel from '$lib/EventTypeLabel.svelte';
import Avatar from '$lib/Avatar.svelte';
import SvelteMarkdown from 'svelte-markdown';
import Link from '$lib/Link.svelte';
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { onMount } from "svelte";
import { bundle, userData } from "$lib/stores.js";
import EventTypeLabel from "$lib/EventTypeLabel.svelte";
import Avatar from "$lib/Avatar.svelte";
import SvelteMarkdown from "svelte-markdown";
import Link from "$lib/Link.svelte";
const renderers = { link: Link }
const renderers = { link: Link };
$: id = getId($page.url.search)
$: e = $bundle ? $bundle.spec.events.find(ev => ev.id === id) : null
$: id = getId($page.url.search);
$: e = $bundle ? $bundle.spec.events.find((ev) => ev.id === id) : null;
function getId (search) {
const searchParams = new URLSearchParams(search)
const cid = searchParams.get('id')
if (!$bundle.spec.events.find(s => s.id === cid)) {
goto('/')
function getId(search) {
const searchParams = new URLSearchParams(search);
const cid = searchParams.get("id");
if (!$bundle.spec.events.find((s) => s.id === cid)) {
goto("/");
}
return cid
return cid;
}
function speakersMap (arr) {
function speakersMap(arr) {
if (!arr) return;
return arr.map(sId => {
return $bundle.spec.speakers.find(sp => sp.id === sId)
})
return arr.map((sId) => {
return $bundle.spec.speakers.find((sp) => sp.id === sId);
});
}
function trackRender (trackId) {
const track = $bundle.spec.tracks.find(t => t.id === trackId)
return track.shortname || track.name
function trackRender(trackId) {
const track = $bundle.spec.tracks.find((t) => t.id === trackId);
return track.shortname || track.name;
}
</script>
<svelte:head>
<title>{e ? e.name : ''} | Události | {$bundle ? $bundle.name : 'UTXO.22'}</title>
<title
>{e ? e.name : ""} | Události | {$bundle ? $bundle.name : "UTXO.22"}</title
>
</svelte:head>
<section class="relative mx-auto py-6 sm:py-10 px-6 max-w-6xl text-blue-web">
@ -56,15 +57,17 @@
<div class="mt-4 mb-2 flex flex-wrap gap-4">
{#each speakersMap(e.speakers) as s}
<div class="flex gap-2">
<Avatar speaker={s} size='semi-small' />
<div class="m-auto"><a href="/lide?id={s.id}" class="text-xl">{s.name}</a></div>
<Avatar speaker={s} size="semi-small" />
<div class="m-auto">
<a href="/lide?id={s.id}" class="text-xl">{s.name}</a>
</div>
</div>
{/each}
</div>
{/if}
<div class="mt-8">
{#if e.description}
<SvelteMarkdown source={e.description} renderers={renderers}/>
<SvelteMarkdown source={e.description} {renderers} />
{/if}
</div>
{/if}

Zobrazit soubor

@ -3,268 +3,299 @@
</script>
<script>
import { onMount, onDestroy } from 'svelte';
import { faker } from '@faker-js/faker';
import { orderTicketForm, apiStatus, userData, userDataLocal } from '$lib/stores';
import { onMount, onDestroy } from "svelte";
import { faker } from "@faker-js/faker";
import {
orderTicketForm,
apiStatus,
userData,
userDataLocal,
} from "$lib/stores";
//import btcPay from 'https://btcpay.utxo.cz/modal/btcpay.js';
import api from '$lib/api.js';
import makeBlockie from 'ethereum-blockies-base64';
import { format, formatDistanceToNow } from 'date-fns';
import { cs } from 'date-fns/locale/index.js'
import api from "$lib/api.js";
import makeBlockie from "ethereum-blockies-base64";
import { format, formatDistanceToNow } from "date-fns";
import { cs } from "date-fns/locale/index.js";
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { loadOrders, loadApiStatus } from '$lib/orders';
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { loadOrders, loadApiStatus } from "$lib/orders";
const orderTicketFormLS = localStorage.getItem('orderTicketForm')
let parsed = JSON.parse(orderTicketFormLS)
const orderTicketFormLS = localStorage.getItem("orderTicketForm");
let parsed = JSON.parse(orderTicketFormLS);
if (parsed && parsed.__v === $orderTicketForm.__v) {
orderTicketForm.set(parsed)
orderTicketForm.set(parsed);
}
let forceShow = false
let loading = true
let formProcessing = false
$: showOrder = !(($userDataLocal.orders && $userDataLocal.orders.length > 0) || ($userDataLocal.tickets && $userDataLocal.tickets.length > 0))
$: orders = $userDataLocal.orders
$: tickets = $userDataLocal.tickets
let forceShow = false;
let loading = true;
let formProcessing = false;
$: showOrder = !(
($userDataLocal.orders && $userDataLocal.orders.length > 0) ||
($userDataLocal.tickets && $userDataLocal.tickets.length > 0)
);
$: orders = $userDataLocal.orders;
$: tickets = $userDataLocal.tickets;
let periodic15 = null
let periodic60 = null
let periodic15 = null;
let periodic60 = null;
onMount(async () => {
const sp = $page.url.searchParams
if (sp.get('done')) {
goto('/gratulujeme');
const sp = $page.url.searchParams;
if (sp.get("done")) {
goto("/gratulujeme");
}
if (sp.get('id') && sp.get('secret')) {
userData.update(ud => {
if (sp.get("id") && sp.get("secret")) {
userData.update((ud) => {
if (!ud.tickets) ud.tickets = [];
ud.tickets.push([ sp.get('id'), sp.get('secret') ].join(':'))
return ud
})
await loadOrders($userData)
goto('/vstupenky');
ud.tickets.push([sp.get("id"), sp.get("secret")].join(":"));
return ud;
});
await loadOrders($userData);
goto("/vstupenky");
}
loading = false
loading = false;
periodic15 = setInterval(() => {
loadApiStatus()
}, 15 * 1000)
loadApiStatus();
}, 15 * 1000);
periodic60 = setInterval(() => {
loadOrders($userData)
}, 60 * 1000)
})
loadOrders($userData);
}, 60 * 1000);
});
onDestroy(() => {
clearInterval(periodic15)
clearInterval(periodic60)
})
clearInterval(periodic15);
clearInterval(periodic60);
});
faker.locale = 'cz';
let orderError = null
faker.locale = "cz";
let orderError = null;
$: count = $apiStatus ? [...Array($apiStatus.config.maxTickets).keys()].map(i => i+1) : [];
$: count = $apiStatus
? [...Array($apiStatus.config.maxTickets).keys()].map((i) => i + 1)
: [];
function selectPaymentMethod (el) {
orderTicketForm.update(f => {
f.paymentMethod = el.target.value
return f
})
function selectPaymentMethod(el) {
orderTicketForm.update((f) => {
f.paymentMethod = el.target.value;
return f;
});
}
function ticketFormCountArray (count) {
return [...Array(count).keys()].map(i => i)
function ticketFormCountArray(count) {
return [...Array(count).keys()].map((i) => i);
}
function processOrderError (error) {
if (typeof error === 'string') {
return { title: error }
function processOrderError(error) {
if (typeof error === "string") {
return { title: error };
}
const out = { title: error.title }
const out = { title: error.title };
if (error.formErrors && error.formErrors.length > 0) {
let suberrs = []
if (error.formErrors.find(fe => fe.instancePath === '/email')) {
suberrs.push(`Neplatný email`)
let suberrs = [];
if (error.formErrors.find((fe) => fe.instancePath === "/email")) {
suberrs.push(`Neplatný email`);
}
let ticket = null
if (error.formErrors.find(fe => ticket = fe.instancePath.match(/\/tickets\/(\d+)\/twitter/))) {
suberrs.push(`Neplatný Twitter účet (Vstupenka #${Number(ticket[1])+1}).`)
let ticket = null;
if (
error.formErrors.find(
(fe) => (ticket = fe.instancePath.match(/\/tickets\/(\d+)\/twitter/))
)
) {
suberrs.push(
`Neplatný Twitter účet (Vstupenka #${Number(ticket[1]) + 1}).`
);
}
out.title = 'Nesprávně vyplněný formulář: ' + (suberrs.length > 0 ? suberrs.join(',') : 'neznámá chyba')
out.debug = error.formErrors
out.title =
"Nesprávně vyplněný formulář: " +
(suberrs.length > 0 ? suberrs.join(",") : "neznámá chyba");
out.debug = error.formErrors;
}
if (!out.title) {
out.title = 'Neznámá chyba, zkuste to prosím později.'
out.title = "Neznámá chyba, zkuste to prosím později.";
}
console.log(out)
return out
console.log(out);
return out;
}
async function submitOrderHandler () {
async function submitOrderHandler() {
// order process
formProcessing = true
formProcessing = true;
// clean previous errors
$: orderError = null
$: orderError = null;
const cloned = Object.assign({}, $orderTicketForm)
cloned.tipCustom = Number(cloned.tipCustom)
cloned.totalPrice = totalPrice
const resp = await api.submitOrder(cloned)
const cloned = Object.assign({}, $orderTicketForm);
cloned.tipCustom = Number(cloned.tipCustom);
cloned.totalPrice = totalPrice;
const resp = await api.submitOrder(cloned);
if (resp.error) {
$: orderError = processOrderError(resp.error)
formProcessing = false
$: orderError = processOrderError(resp.error);
formProcessing = false;
return null;
}
if (!resp.ok || !resp.payment.url) {
return null
return null;
}
// reset tickets in form
orderTicketForm.update(of => {
of.count = 1
of.tickets = []
return of
})
orderTicketForm.update((of) => {
of.count = 1;
of.tickets = [];
return of;
});
// add order to user data
userData.update(ud => {
userData.update((ud) => {
if (!ud.orders) {
ud.orders = []
ud.orders = [];
}
ud.orders.push(resp.orderId)
return ud
})
ud.orders.push(resp.orderId);
return ud;
});
window.location.replace(resp.payment.url)
window.location.replace(resp.payment.url);
}
let ticketsNum = 0
let ticketsNum = 0;
orderTicketForm.subscribe(f => {
orderTicketForm.subscribe((f) => {
if (f.count !== ticketsNum) {
ticketsNum = f.count
orderTicketForm.update(cf => {
f.tickets = f.tickets.slice(0, f.count)
ticketsNum = f.count;
orderTicketForm.update((cf) => {
f.tickets = f.tickets.slice(0, f.count);
for (let i = 0; i < f.count; i++) {
console.log(i)
console.log(i);
if (!f.tickets[i]) {
f.tickets[i] = { name: '', org: '', twitter: '', extras: { kino: false } }
f.tickets[i] = {
name: "",
org: "",
twitter: "",
extras: { kino: false },
};
}
}
return f
})
return f;
});
}
localStorage.setItem('orderTicketForm', JSON.stringify(f))
})
localStorage.setItem("orderTicketForm", JSON.stringify(f));
});
function changeTip (perc) {
function changeTip(perc) {
return () => {
orderTicketForm.update(of => {
of.tipPercent = perc
of.tipCustom = ''
return of
})
}
orderTicketForm.update((of) => {
of.tipPercent = perc;
of.tipCustom = "";
return of;
});
};
}
orderTicketForm.subscribe(of => {
const val = Number(of.tipCustom)
orderTicketForm.subscribe((of) => {
const val = Number(of.tipCustom);
if (val === NaN || !val || val < 1 || !String(val).match(/^\d+$/)) {
of.tipCustom = ''
}
else if (val && val > 0 && of.tipPercent > 0) {
of.tipPercent = 0
of.tipCustom = "";
} else if (val && val > 0 && of.tipPercent > 0) {
of.tipPercent = 0;
}
if (orderError) {
orderError = false
orderError = false;
}
return of
})
return of;
});
function roundPrice (num) {
return Math.round(num * 100) / 100
function roundPrice(num) {
return Math.round(num * 100) / 100;
}
function extrasStatsRender (form) {
let obj = {}
$apiStatus.config.extras.forEach(ex => {
obj[ex.id] = { count: 0, sum: 0, price: ex.price }
form.tickets.forEach(t => {
function extrasStatsRender(form) {
let obj = {};
$apiStatus.config.extras.forEach((ex) => {
obj[ex.id] = { count: 0, sum: 0, price: ex.price };
form.tickets.forEach((t) => {
if (t.extras[ex.id]) {
obj[ex.id].count++
obj[ex.id].sum = obj[ex.id].count * ex.price
obj[ex.id].count++;
obj[ex.id].sum = obj[ex.id].count * ex.price;
}
})
})
let arr = Object.keys(obj).map(key => Object.assign({ id: key }, obj[key]))
});
});
let arr = Object.keys(obj).map((key) =>
Object.assign({ id: key }, obj[key])
);
return {
sum: arr.reduce((p, c) => p + c.sum, 0),
arr
}
arr,
};
}
$: extrasStats = $apiStatus ? extrasStatsRender($orderTicketForm) : {}
$: ticketPrice = $apiStatus && $apiStatus.wave ? $orderTicketForm.count * $apiStatus.wave.price : 0
$: totalBeforeTip = ticketPrice + extrasStats.sum
$: tip = roundPrice($orderTicketForm.tipPercent > 0 ? $orderTicketForm.tipPercent * (totalBeforeTip/100) : Number($orderTicketForm.tipCustom))
$: totalPrice = totalBeforeTip + tip
$: extrasStats = $apiStatus ? extrasStatsRender($orderTicketForm) : {};
$: ticketPrice =
$apiStatus && $apiStatus.wave
? $orderTicketForm.count * $apiStatus.wave.price
: 0;
$: totalBeforeTip = ticketPrice + extrasStats.sum;
$: tip = roundPrice(
$orderTicketForm.tipPercent > 0
? $orderTicketForm.tipPercent * (totalBeforeTip / 100)
: Number($orderTicketForm.tipCustom)
);
$: totalPrice = totalBeforeTip + tip;
const orderStatuses = {
new: {
name: 'Čeká na platbu',
icon: 'fa-solid fa-clock',
color: 'text-yellow-600',
name: "Čeká na platbu",
icon: "fa-solid fa-clock",
color: "text-yellow-600",
text: (o) => {
return 'Tato objednávka čeká na platbu'
return "Tato objednávka čeká na platbu";
},
},
done: {
name: 'Zaplacená',
icon: 'fa-solid fa-check',
color: 'text-green-600'
name: "Zaplacená",
icon: "fa-solid fa-check",
color: "text-green-600",
},
cancelled: {
name: 'Zrušená',
icon: 'fa-solid fa-ban',
color: 'text-gray-600'
name: "Zrušená",
icon: "fa-solid fa-ban",
color: "text-gray-600",
},
expired: {
name: 'Expirovaná',
icon: 'fa-solid fa-clock',
color: 'text-gray-600'
}
name: "Expirovaná",
icon: "fa-solid fa-clock",
color: "text-gray-600",
},
};
async function orderActionHandler(id, action) {
const resp = await api.apiCall(
"updateOrder",
{ method: "POST" },
{ id, action }
);
loadOrders($userData);
loadApiStatus();
}
async function orderActionHandler (id, action) {
const resp = await api.apiCall('updateOrder', { method: 'POST' }, { id, action })
loadOrders($userData)
loadApiStatus()
}
function removeOrder (id) {
userData.update(ud => {
console.log('change', ud.orders, id)
let index = ud.orders.indexOf(id)
function removeOrder(id) {
userData.update((ud) => {
console.log("change", ud.orders, id);
let index = ud.orders.indexOf(id);
if (index >= 0) {
if (ud.orders.length === 1) {
ud.orders = []
ud.orders = [];
} else {
ud.orders.splice(index, 1)
ud.orders.splice(index, 1);
}
}
return ud
})
loadOrders($userData)
return ud;
});
loadOrders($userData);
}
</script>
<svelte:head>
@ -281,21 +312,37 @@
<div class="mt-4 flex flex-wrap gap-3">
{#each tickets as ticket}
<div class="w-full sm:w-80">
<div class="h-2 bg-blue-web rounded-t-md shadow-md"></div>
<div class="border-l border-b border-r p-4 rounded-b-md shadow-md border-blue-web">
<div class="h-2 bg-blue-web rounded-t-md shadow-md" />
<div
class="border-l border-b border-r p-4 rounded-b-md shadow-md border-blue-web"
>
<div class="flex gap-3 text-sm">
<div class="w-11 h-11 rounded-md" style="background-image: url({makeBlockie(ticket.avatarHash)}); background-size: 100% 100%;"></div>
<div
class="w-11 h-11 rounded-md"
style="background-image: url({makeBlockie(
ticket.avatarHash
)}); background-size: 100% 100%;"
/>
<div class="w-auto">
<div class="no-flex">
<span class="px-2 py-1 bg-blue-web text-white rounded w-auto">#{ticket.id}</span>
<span
class="px-2 py-1 bg-blue-web text-white rounded w-auto"
>#{ticket.id}</span
>
</div>
<div class="mt-1.5">Běžná vstupenka</div>
</div>
</div>
{#if ticket.data}
<div class="mt-2">
{#if ticket.data.name}{ticket.data.name}{:else}<span class="italic">Anonym</span>{/if} {#if ticket.data.org}({ticket.data.org}){/if}
{#if ticket.data.twitter}<a href="https://twitter.com/{ticket.data.twitter}" target="_blank"><i class="fa-brands fa-twitter"></i></a>{/if}
{#if ticket.data.name}{ticket.data.name}{:else}<span
class="italic">Anonym</span
>{/if}
{#if ticket.data.org}({ticket.data.org}){/if}
{#if ticket.data.twitter}<a
href="https://twitter.com/{ticket.data.twitter}"
target="_blank"><i class="fa-brands fa-twitter" /></a
>{/if}
</div>
{/if}
</div>
@ -313,32 +360,71 @@
<div class="mt-4">
{#each orders as order}
<div class="mb-2 px-3 py-2 border rounded-lg">
{#if ['expired', 'cancelled'].includes(order.status)}
<div class="float-right"><a class="cursor-pointer" on:click={() => removeOrder(order.id)}><i class="fa-solid fa-xmark"></i></a></div>
{#if ["expired", "cancelled"].includes(order.status)}
<div class="float-right">
<a
class="cursor-pointer"
on:click={() => removeOrder(order.id)}
><i class="fa-solid fa-xmark" /></a
>
</div>
{/if}
<div class="flex flex-wrap gap-3 text-sm">
<div class="px-2 py-1 bg-gray-200 rounded">#{order.id}</div>
<div class="my-auto uppercase {orderStatuses[order.status].color ? (orderStatuses[order.status].color) : ''}">
<div
class="my-auto uppercase {orderStatuses[order.status].color
? orderStatuses[order.status].color
: ''}"
>
{#if orderStatuses[order.status].icon}
<i class="{orderStatuses[order.status].icon} mr-0.5"></i>
<i class="{orderStatuses[order.status].icon} mr-0.5" />
{/if}
{orderStatuses[order.status].name}
{#if order.status === 'new'}
&nbsp;- expirace {format(new Date(order.expiration), 'd.M. H:mm')} (zbývá {formatDistanceToNow(new Date(order.expiration), { locale: cs, includeSeconds: true })})
{#if order.status === "new"}
&nbsp;- expirace {format(
new Date(order.expiration),
"d.M. H:mm"
)} (zbývá {formatDistanceToNow(
new Date(order.expiration),
{ locale: cs, includeSeconds: true }
)})
{/if}
</div>
</div>
<div class="mt-2 text-sm flex gap-2">
<div>Vytvořeno: <span class="font-bold">{format(new Date(order.created), 'd.M.y H:mm')}</span><br/></div>
<div>Částka: <span class="font-bold">{order.amount}</span><br/></div>
<div>Platební metoda: <span class="font-bold">{$apiStatus.config.paymentMethods.find(pm => pm.id === order.paymentMethod).shortname}</span></div>
<div>
Vytvořeno: <span class="font-bold"
>{format(new Date(order.created), "d.M.y H:mm")}</span
><br />
</div>
<div>
Částka: <span class="font-bold">{order.amount}</span><br
/>
</div>
<div>
Platební metoda: <span class="font-bold"
>{$apiStatus.config.paymentMethods.find(
(pm) => pm.id === order.paymentMethod
).shortname}</span
>
</div>
</div>
{#if orderStatuses[order.status].text}
<div class="mt-2 italic">{orderStatuses[order.status].text(order)}</div>
<div class="mt-2 italic">
{orderStatuses[order.status].text(order)}
</div>
{#if order.actions}
<div class="flex gap-3">
{#each order.actions as action}
<div class="mt-2"><a href="{action.url}" class="underline hover:no-underline cursor-pointer" on:click={() => orderActionHandler(order.id, action.remote)}>{action.name}</a></div>
<div class="mt-2">
<a
href={action.url}
class="underline hover:no-underline cursor-pointer"
on:click={() =>
orderActionHandler(order.id, action.remote)}
>{action.name}</a
>
</div>
{/each}
</div>
{/if}
@ -352,45 +438,79 @@
<h1 class="uppercase text-2xl font-bold">Nákup vstupenek</h1>
<div class="mt-4 mb-8">
<div class="flex w-full uppercase text-sm mb-2">
<div class="flex-1">{$apiStatus.wave.name} ({$apiStatus.wave.price} Kč)</div>
<div class="justify-end">Zbývá {$apiStatus.wave.live.left} / {$apiStatus.wave.count}</div>
<div class="flex-1">
{$apiStatus.wave.name} ({$apiStatus.wave.price} Kč)
</div>
<div class="justify-end">
Zbývá {$apiStatus.wave.live.left} / {$apiStatus.wave.count}
</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-utxo-gradient h-2.5 rounded-full transition-all" style="width: {$apiStatus.wave.live.perc}%"></div>
<div
class="bg-utxo-gradient h-2.5 rounded-full transition-all"
style="width: {$apiStatus.wave.live.perc}%"
/>
</div>
</div>
{#if $apiStatus.wave.live.left <= 0}
<div class="mt-2">Aktuální vlna je vyprodaná. {#if $apiStatus.wave.live.waiting > 0}Zarezervované a nezaplacené vstupenky ({$apiStatus.wave.live.waiting}) se postupně vrací do prodeje.{/if}</div>
{:else}
{#if !showOrder && !forceShow}
<div class="mt-4">
<button class="border rounded-full border-[#E16A61] hover:border-0 hover:p-px hover:bg-utxo-gradient hover:text-white" on:click={() => forceShow = true}><div class="px-6 py-2">Nakoupit další vstupenky</div></button>
<div class="mt-2">
Aktuální vlna je vyprodaná. {#if $apiStatus.wave.live.waiting > 0}Zarezervované
a nezaplacené vstupenky ({$apiStatus.wave.live.waiting}) se
postupně vrací do prodeje.{/if}
</div>
{:else}
{#if !$apiStatus.wave}
{:else if !showOrder && !forceShow}
<div class="mt-4">
<button
class="border rounded-full border-[#E16A61] hover:border-0 hover:p-px hover:bg-utxo-gradient hover:text-white"
on:click={() => (forceShow = true)}
><div class="px-6 py-2">Nakoupit další vstupenky</div></button
>
</div>
{:else if !$apiStatus.wave}
<div class="mt-4">V současné době nelze zakoupit vstupenky.</div>
{:else}
<div class="p-3 bg-blue-web-light rounded-md mt-6 text-blue-web shadow-md">
<div
class="p-3 bg-blue-web-light rounded-md mt-6 text-blue-web shadow-md"
>
<div class="sm:m-2 border rounded-md p-4 shadow bg-white mb-6">
<div class="mb-4">
Název akce: <span class="font-bold">UTXO.22</span><br/>
Datum konání: <span class="font-bold">4. - 5. červen 2022</span> (sobota - neděle)<br/>
Místo: <span class="font-bold">Gabriel Loci</span>, Praha 5<br/><br/>
Aktuální cena vstupenky: <span class="font-bold">{$apiStatus.wave.price}</span> / osobu ({ $apiStatus.wave.name })
Název akce: <span class="font-bold">UTXO.22</span><br />
Datum konání: <span class="font-bold">4. - 5. červen 2022</span>
(sobota - neděle)<br />
Místo: <span class="font-bold">Gabriel Loci</span>, Praha 5<br
/><br />
Aktuální cena vstupenky:
<span class="font-bold">{$apiStatus.wave.price}</span>
/ osobu ({$apiStatus.wave.name})
</div>
<div class="md:flex gap-3">
<div class="md:w-1/2 p-2">
<div class="font-bold uppercase">&nbsp;Vstupenka obsahuje:</div>
<div class="font-bold uppercase">
&nbsp;Vstupenka obsahuje:
</div>
<div class="mt-4">
<div class="mb-2">• přístup na všechny přednášky, workshopy a další události v rámci konference</div>
<div class="mb-2">• platnost oba dva konferenční dny (sobota + neděle)</div>
<div class="mb-2">• přístup na <span class="font-bold">UTXO.Party</span> v sobotu večer</div>
<div class="mb-2">
• přístup na všechny přednášky, workshopy a další události
v rámci konference
</div>
<div class="mb-2">
• platnost oba dva konferenční dny (sobota + neděle)
</div>
<div class="mb-2">
• přístup na <span class="font-bold">UTXO.Party</span> v sobotu
večer
</div>
</div>
</div>
<div class="md:w-1/2 p-2 md:mt-0 mt-3">
<div class="font-bold uppercase">&nbsp;Vstupenka NEobsahuje:</div>
<div class="font-bold uppercase">
&nbsp;Vstupenka NEobsahuje:
</div>
<div class="mt-4">
<div class="mb-2">• jídlo a pití - občerstvení bude možné zakoupit na místě (platba kartou nebo Lightning)</div>
<div class="mb-2">
• jídlo a pití - občerstvení bude možné zakoupit na místě
(platba kartou nebo Lightning)
</div>
</div>
</div>
</div>
@ -398,49 +518,110 @@
<div class="p-2">
<div>
<div class="uppercase text-sm font-bold">Email</div>
<div class="mt-2 text-sm">Kontaktní email, na který budou zaslány vstupenky.</div>
<div class="mt-2"><input type="text" class="border border-blue-web rounded-md p-2 w-full lg:w-1/2 text-blue-web" bind:value={$orderTicketForm.email} /></div>
<div class="mt-2 text-sm">
Kontaktní email, na který budou zaslány vstupenky.
</div>
<div class="mt-2">
<input
type="text"
class="border border-blue-web rounded-md p-2 w-full lg:w-1/2 text-blue-web"
bind:value={$orderTicketForm.email}
/>
</div>
</div>
<div class="mt-6">
<div class="uppercase text-sm font-bold">Počet vstupenek</div>
<div class="mt-2 flex gap-3">
<select name="count" class="border border-blue-web rounded-md p-2 text-blue-web bg-white" bind:value={$orderTicketForm.count}>
<select
name="count"
class="border border-blue-web rounded-md p-2 text-blue-web bg-white"
bind:value={$orderTicketForm.count}
>
{#each count as i}
<option value={i}>{i}</option>
{/each}
</select>
<div class="my-auto">
× <span class="font-bold">{$apiStatus.wave.price}</span> (cena vstupenky)
× <span class="font-bold">{$apiStatus.wave.price}</span> (cena
vstupenky)
</div>
</div>
</div>
<div class="mt-6">
<div class="uppercase text-sm font-bold">Vstupenky a příplatky</div>
<div class="mt-2 text-sm">Informace, které budou vytištěné na Vaší konferenční jmenovku. Tyto údaje jsou nepovinné a je možné je změnit později.</div>
<div class="uppercase text-sm font-bold">
Vstupenky a příplatky
</div>
<div class="mt-2 text-sm">
Informace, které budou vytištěné na Vaší konferenční jmenovku.
Tyto údaje jsou nepovinné a je možné je změnit později.
</div>
</div>
<div class="mt-2">
{#each ticketFormCountArray($orderTicketForm.count) as i}
<div class="p-4 bg-utxo-gradient text-white rounded-md mb-6 shadow-md">
<div
class="p-4 bg-utxo-gradient text-white rounded-md mb-6 shadow-md"
>
<div class="">
<div class="uppercase font-bold">Vstupenka #{i+1}</div>
<div class="uppercase font-bold">Vstupenka #{i + 1}</div>
<div class="md:flex gap-2">
<div class="mt-2 flex-1">
<div class="uppercase text-sm">Jméno (Přezdívka)</div>
<div class="mt-2"><input type="text" maxlength="25" class="border border-blue-web rounded-md p-2 w-full text-blue-web" bind:value={$orderTicketForm.tickets[i].name} placeholder="{faker.name.findName()}" /></div>
<div class="mt-2">
<input
type="text"
maxlength="25"
class="border border-blue-web rounded-md p-2 w-full text-blue-web"
bind:value={$orderTicketForm.tickets[i].name}
placeholder={faker.name.findName()}
/>
</div>
</div>
<div class="mt-2 flex-1">
<div class="uppercase text-sm">Organizace (Firma)</div>
<div class="mt-2"><input type="text" maxlength="25" class="border border-blue-web rounded-md p-2 w-full text-blue-web" bind:value={$orderTicketForm.tickets[i].org} placeholder="{faker.company.companyName()}" /></div>
<div class="uppercase text-sm">
Organizace (Firma)
</div>
<div class="mt-2">
<input
type="text"
maxlength="25"
class="border border-blue-web rounded-md p-2 w-full text-blue-web"
bind:value={$orderTicketForm.tickets[i].org}
placeholder={faker.company.companyName()}
/>
</div>
</div>
<div class="mt-2">
<div class="uppercase text-sm">Twitter účet</div>
<div class="mt-2"><input type="text" maxlength="25" class="border border-blue-web rounded-md p-2 w-full text-blue-web" bind:value={$orderTicketForm.tickets[i].twitter} placeholder="@{faker.internet.userName()}" /></div>
<div class="mt-2">
<input
type="text"
maxlength="25"
class="border border-blue-web rounded-md p-2 w-full text-blue-web"
bind:value={$orderTicketForm.tickets[i].twitter}
placeholder="@{faker.internet.userName()}"
/>
</div>
</div>
<div class="uppercase text-sm font-bold mt-3">Volitelné příplatky</div>
</div>
<div class="uppercase text-sm font-bold mt-3">
Volitelné příplatky
</div>
<div class="mt-1">
{#each $apiStatus.config.extras as ex}
<div><label class="cursor-pointer"><input type="checkbox" class="mr-0.5 cursor-pointer" bind:checked={$orderTicketForm.tickets[i].extras[ex.id]} /> <span class="font-semibold">{ex.name} ({ex.price} Kč)</span></label> - <span class="text-sm">{ex.desc}</span></div>
<div>
<label class="cursor-pointer"
><input
type="checkbox"
class="mr-0.5 cursor-pointer"
bind:checked={$orderTicketForm.tickets[i]
.extras[ex.id]}
/>
<span class="font-semibold"
>{ex.name} ({ex.price} Kč)</span
></label
>
- <span class="text-sm">{ex.desc}</span>
</div>
{/each}
</div>
</div>
@ -448,12 +629,30 @@
{/each}
</div>
<div class="mt-6">
<div class="uppercase text-sm font-bold">Dýško pro organizátory</div>
<div class="uppercase text-sm font-bold">
Dýško pro organizátory
</div>
<div class="mt-2 flex flex-wrap gap-2">
{#each $apiStatus.config.tipPercentages as tp}
<button class="block py-1.5 px-4 min-w-16 {$orderTicketForm.tipPercent === tp[0] && $orderTicketForm.tipCustom < 1 ? (tp[0] === 0 ? 'bg-gray-400' : 'bg-blue-web' )+' rounded-full text-white shadow-lg' : 'border border-solid border-blue-web rounded-full'}" on:click={changeTip(tp[0])}>{tp[1] || tp[0] + '%'}</button>
<button
class="block py-1.5 px-4 min-w-16 {$orderTicketForm.tipPercent ===
tp[0] && $orderTicketForm.tipCustom < 1
? (tp[0] === 0 ? 'bg-gray-400' : 'bg-blue-web') +
' rounded-full text-white shadow-lg'
: 'border border-solid border-blue-web rounded-full'}"
on:click={changeTip(tp[0])}>{tp[1] || tp[0] + "%"}</button
>
{/each}
<div class="px-2 py-1 {$orderTicketForm.tipCustom > 0 ? 'px-5 border border-blue-web rounded-full bg-blue-web text-white' : ''}">Jiná částka: <input class="border border border-blue-web rounded-md px-2 py-1 text-blue-web w-16" bind:value={$orderTicketForm.tipCustom} /></div>
<div
class="px-2 py-1 {$orderTicketForm.tipCustom > 0
? 'px-5 border border-blue-web rounded-full bg-blue-web text-white'
: ''}"
>
Jiná částka: <input
class="border border border-blue-web rounded-md px-2 py-1 text-blue-web w-16"
bind:value={$orderTicketForm.tipCustom}
/> Kč
</div>
</div>
</div>
<div class="mt-8">
@ -461,67 +660,113 @@
<div class="mt-2">
{#each $apiStatus.config.paymentMethods as pm}
<div class="mb-2">
<label class="cursor-pointer"><input checked={$orderTicketForm.paymentMethod === pm.id} name="paymentMethod" value="{pm.id}" type="radio" on:change={selectPaymentMethod} class="cursor-pointer" /> {pm.name}</label>
<label class="cursor-pointer"
><input
checked={$orderTicketForm.paymentMethod === pm.id}
name="paymentMethod"
value={pm.id}
type="radio"
on:change={selectPaymentMethod}
class="cursor-pointer"
/>
{pm.name}</label
>
</div>
{/each}
</div>
</div>
<div class="mt-6 py-3 px-4 bg-white rounded-md shadow-md text-sm">
<div class="uppercase text-sm font-bold">Shrnutí objednávky</div>
<div class="uppercase text-sm font-bold">
Shrnutí objednávky
</div>
<div class="mt-1.5">
<table cellpadding="5" class="table-auto">
<tr>
<td>{$orderTicketForm.count}× UTXO.22 vstupenka ({$apiStatus.wave.price} Kč)</td>
<td
>{$orderTicketForm.count}× UTXO.22 vstupenka ({$apiStatus
.wave.price} Kč)</td
>
<td>{ticketPrice}</td>
</tr>
{#if extrasStats.sum > 0}
{#each extrasStats.arr as es}
<tr>
<td>{es.count}× {$apiStatus.config.extras.find(e => e.id === es.id).shortname} ({es.price} Kč)</td>
<td
>{es.count}× {$apiStatus.config.extras.find(
(e) => e.id === es.id
).shortname} ({es.price} Kč)</td
>
<td>{es.sum}</td>
</tr>
{/each}
{/if}
{#if tip > 0}
<tr>
<td>dýško pro organizátory ({$orderTicketForm.tipPercent > 0 ? $orderTicketForm.tipPercent + '%' : $orderTicketForm.tipCustom + ' Kč'})</td>
<td
>dýško pro organizátory ({$orderTicketForm.tipPercent >
0
? $orderTicketForm.tipPercent + "%"
: $orderTicketForm.tipCustom + " Kč"})</td
>
<td>{tip}</td>
</tr>
{/if}
<tr>
<th align="right">Celkem:</th>
<th class="font-bold">{totalPrice}<th>
</tr>
<th class="font-bold">{totalPrice}</th><th /></tr
>
</table>
</div>
<div class="mt-1.5">Platební metoda: <span class="font-bold">{#if $orderTicketForm.paymentMethod === 'btcpay'}Bitcoin{:else}Platební karta{/if}</span></div>
<div class="mt-1.5">
Platební metoda: <span class="font-bold"
>{#if $orderTicketForm.paymentMethod === "btcpay"}Bitcoin{:else}Platební
karta{/if}</span
>
</div>
</div>
<div class="mt-6">
<div class="flex gap-3">
{#if !formProcessing}
<div><button class="{formProcessing ? 'bg-gray-800' : 'hover:bg-utxo-gradient bg-[#E16A61]'} text-white font-semibold rounded-full shadow-md" disabled={formProcessing} on:click={submitOrderHandler}><div class="py-2 px-4">Odeslat objednávku - zaplatit {totalPrice}{#if $orderTicketForm.paymentMethod === 'btcpay'}bitcoinem{:else}platební kartou{/if}</div></button></div>
<div>
<button
class="{formProcessing
? 'bg-gray-800'
: 'hover:bg-utxo-gradient bg-[#E16A61]'} text-white font-semibold rounded-full shadow-md"
disabled={formProcessing}
on:click={submitOrderHandler}
><div class="py-2 px-4">
Odeslat objednávku - zaplatit {totalPrice}{#if $orderTicketForm.paymentMethod === "btcpay"}bitcoinem{:else}platební
kartou{/if}
</div></button
>
</div>
{/if}
{#if formProcessing}
<div class="flex gap-3">
<div class=""><img src="/img/Spin-1s-200px.gif" class="w-10" /></div>
<div class="my-auto text-red-600 font-bold">Odesílám objednávku ..</div>
<div class="">
<img src="/img/Spin-1s-200px.gif" class="w-10" />
</div>
<div class="my-auto text-red-600 font-bold">
Odesílám objednávku ..
</div>
</div>
{/if}
</div>
{#if orderError}
<div class="py-2 px-3 mt-4 text-red-600 bg-red-200 rounded-xl shadow-md">Chyba: {orderError.title}</div>
<div
class="py-2 px-3 mt-4 text-red-600 bg-red-200 rounded-xl shadow-md"
>
Chyba: {orderError.title}
</div>
{/if}
</div>
</div>
</div>
{/if}
{/if}
{/if}
</div>
<!--pre>{JSON.stringify($orderTicketForm, null, 2)}</pre>
<pre>{JSON.stringify(extrasStats, null, 2)}</pre -->
{/if}
{/if}
</section>