Deploy to production

MVP #6.1
This commit is contained in:
Matthieu Morin 2023-03-31 19:40:26 +02:00 committed by GitHub
commit 03608e509c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 1978 additions and 779 deletions

View File

@ -1,13 +1,13 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: 'standard-with-typescript',
overrides: [],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {},
env: {
browser: true,
es2021: true
},
extends: 'standard-with-typescript',
overrides: [],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {}
}

View File

@ -1,66 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<meta
name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi"
/>
<title>Erant</title>
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Erant</title>
<!-- Serviceworker registration -->
<script>
if (typeof navigator.serviceWorker !== 'undefined') {
navigator.serviceWorker.register('servicewworker.js')
}
</script>
<!-- Serviceworker registration -->
<script>
if (typeof navigator.serviceWorker !== 'undefined') {
navigator.serviceWorker.register('sw.js')
}
</script>
<!-- Meta Pixel Code -->
<script>
!(function (f, b, e, v, n, t, s) {
if (f.fbq) return
n = f.fbq = function () {
n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments)
}
if (!f._fbq) f._fbq = n
n.push = n
n.loaded = !0
n.version = '2.0'
n.queue = []
t = b.createElement(e)
t.async = !0
t.src = v
s = b.getElementsByTagName(e)[0]
s.parentNode.insertBefore(t, s)
})(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js')
fbq('init', '541491674547104')
fbq('track', 'PageView')
</script>
<noscript><img height="1" width="1" style="display: none" src="https://www.facebook.com/tr?id=541491674547104&ev=PageView&noscript=1" /></noscript>
<!-- End Meta Pixel Code -->
<!-- Meta Pixel Code -->
<script>
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '541491674547104');
fbq('track', 'PageView');
</script>
<noscript><img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id=541491674547104&ev=PageView&noscript=1"
/></noscript>
<!-- End Meta Pixel Code -->
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-8RCL0H1Q7V"></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-8RCL0H1Q7V"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-8RCL0H1Q7V')
</script>
gtag('config', 'G-8RCL0H1Q7V');
</script>
<!-- Google Tag Manager -->
<script>
;(function (w, d, s, l, i) {
w[l] = w[l] || []
w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' })
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : ''
j.async = true
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl
f.parentNode.insertBefore(j, f)
})(window, document, 'script', 'dataLayer', 'GTM-WLHVPKW')
</script>
<!-- End Google Tag Manager -->
</head>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-WLHVPKW');</script>
<!-- End Google Tag Manager -->
</head>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WLHVPKW"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
<body>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WLHVPKW" height="0" width="0" style="display: none; visibility: hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

36
package-lock.json generated
View File

@ -39,9 +39,10 @@
"eslint-plugin-promise": "^6.1.1",
"flowbite-svelte": "^0.28.11",
"postcss": "^8.4.19",
"sass": "^1.56.1",
"sass": "^1.59.3",
"svelte": "^3.52.0",
"svelte-preprocess": "^4.10.7",
"svelte-preprocess-sass": "^2.0.1",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"vite": "^3.2.3"
@ -5823,6 +5824,24 @@
}
}
},
"node_modules/svelte-preprocess-filter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/svelte-preprocess-filter/-/svelte-preprocess-filter-1.0.0.tgz",
"integrity": "sha512-92innv59nyEx24xbfcSurB5ocwC8qFdDtGli/JVMHzJsxyvV2yjQKIcbUqU9VIV5mKUWO2PoY93nncS2yF4ULQ==",
"dev": true
},
"node_modules/svelte-preprocess-sass": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/svelte-preprocess-sass/-/svelte-preprocess-sass-2.0.1.tgz",
"integrity": "sha512-0y4FjRsRWcN7rJeNJnSfZ7LVAz6S7/j9Dg24XFRelr/rjMMjXORdEvXy4r38fUYmyk9Y7yjwlHCiqyGxMHhEbg==",
"dev": true,
"dependencies": {
"svelte-preprocess-filter": "^1.0.0"
},
"peerDependencies": {
"sass": "^1.35.2"
}
},
"node_modules/svelte-preprocess/node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@ -10417,6 +10436,21 @@
}
}
},
"svelte-preprocess-filter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/svelte-preprocess-filter/-/svelte-preprocess-filter-1.0.0.tgz",
"integrity": "sha512-92innv59nyEx24xbfcSurB5ocwC8qFdDtGli/JVMHzJsxyvV2yjQKIcbUqU9VIV5mKUWO2PoY93nncS2yF4ULQ==",
"dev": true
},
"svelte-preprocess-sass": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/svelte-preprocess-sass/-/svelte-preprocess-sass-2.0.1.tgz",
"integrity": "sha512-0y4FjRsRWcN7rJeNJnSfZ7LVAz6S7/j9Dg24XFRelr/rjMMjXORdEvXy4r38fUYmyk9Y7yjwlHCiqyGxMHhEbg==",
"dev": true,
"requires": {
"svelte-preprocess-filter": "^1.0.0"
}
},
"svelte-routing": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/svelte-routing/-/svelte-routing-1.6.0.tgz",

View File

@ -22,9 +22,10 @@
"eslint-plugin-promise": "^6.1.1",
"flowbite-svelte": "^0.28.11",
"postcss": "^8.4.19",
"sass": "^1.56.1",
"sass": "^1.59.3",
"svelte": "^3.52.0",
"svelte-preprocess": "^4.10.7",
"svelte-preprocess-sass": "^2.0.1",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.5",
"vite": "^3.2.3"

View File

@ -2,5 +2,5 @@ const autoprefixer = require('autoprefixer')
const tailwind = require('tailwindcss')
module.exports = {
plugins: [tailwind(), autoprefixer()],
plugins: [tailwind(), autoprefixer()]
}

View File

@ -0,0 +1,93 @@
Copyright 2013 The Redacted Project Authors (https://github.com/christiannaths/redacted-font)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,93 @@
Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name Source.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,53 +1,4 @@
// This is the service worker with the combined offline experience (Offline page + Offline copy of pages)
import { workbox } from 'https://storage.googleapis.com/workbox-cdn/releases/5.1.4/workbox-sw.js'
const CACHE = 'pwabuilder-offline-page'
self.addEventListener('install', (e) => { })
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.4/workbox-sw.js')
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = '/offline.html'
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => cache.add(offlineFallbackPage))
)
})
if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable()
}
workbox.routing.registerRoute(
/\/*/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE
})
)
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResp = await event.preloadResponse
if (preloadResp) {
return preloadResp
}
const networkResp = await fetch(event.request)
return networkResp
} catch (error) {
const cache = await caches.open(CACHE)
const cachedResp = await cache.match(offlineFallbackPage)
return cachedResp
}
})())
}
})
self.addEventListener('fetch', (e) => { })

View File

@ -1,4 +1,55 @@
//self.addEventListener("install", (e) => { })
// This is the service worker with the combined offline experience (Offline page + Offline copy of pages)
import workbox from 'workbox-sw'
//self.addEventListener('fetch', (e) => { })
const CACHE = 'pwabuilder-offline-page'
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.4/workbox-sw.js')
// TODO: replace the following with the correct offline fallback page i.e.: const offlineFallbackPage = "offline.html";
const offlineFallbackPage = '/offline.html'
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
self.addEventListener('install', async (event) => {
event.waitUntil(
caches.open(CACHE)
.then((cache) => cache.add(offlineFallbackPage))
)
})
if (workbox.navigationPreload.isSupported()) {
workbox.navigationPreload.enable()
}
workbox.routing.registerRoute(
/\/*/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: CACHE
})
)
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResp = await event.preloadResponse
if (preloadResp) {
return preloadResp
}
const networkResp = await fetch(event.request)
return networkResp
} catch (error) {
const cache = await caches.open(CACHE)
const cachedResp = await cache.match(offlineFallbackPage)
return cachedResp
}
})())
}
})

View File

@ -13,46 +13,12 @@
loading={Loading}
error={Error}
routes={[
//experience
{
path: '/',
component: () => import('$routes/homepage/homepage.svelte'),
layout: NavigationBarLayout,
},
{
path: '/error',
component: () => import('$routes/error.svelte'),
},
{
path: '/scanner',
component: () => import('$routes/qrscanner/qrscanner.svelte'),
},
{
path: '/test',
component: () => import('$routes/test.svelte'),
},
{
path: '/map',
component: () => import('$root/src/routes/map/map.svelte'),
},
{
path: '/explore',
component: () => import('$routes/explore/explore.svelte'),
layout: NavigationBarLayout,
},
{
path: '/profile/',
component: () => import('$src/__error.svelte'),
},
{
path: '/profile/:erantId',
component: () => import('$routes/profile/profile.svelte'),
layout: NavigationBarLayout,
},
{
path: '/profile/setting/:function',
component: () => import('$routes/profile/profile-functions.svelte'),
layout: ArrowBackLayout,
path: '/:gameurl',
component: () => import('$routes/game/experience.svelte'),
},
//authorization
{
path: '/login',
component: () => import('$routes/authorization/login/log_in.svelte'),
@ -65,7 +31,6 @@
path: '/register/failed',
component: () => import('$routes/authorization/register/registerFailed.svelte'),
},
{
path: '/register/emailverification/:erantId',
component: () => import('$routes/authorization/register/emailVerification.svelte'),
@ -75,9 +40,54 @@
component: () => import('$routes/authorization/register/createAccount.svelte'),
},
{
path: '/:gameurl',
component: () => import('$routes/game/game.svelte'),
path: '/forgot-pswd',
component: () => import('$routes/authorization/forgottonPassword/forgot-pswd.svelte'),
},
...[
{
path: '/',
component: () => import('$routes/homepage/homepage.svelte'),
},
{
path: '/explore',
component: () => import('$routes/explore/explore.svelte'),
},
{
path: '/profile/:erantId',
component: () => import('$routes/profile/profile.svelte'),
},
].map((routes) => {
return { ...routes, layout: NavigationBarLayout }
}),
...[
{
path: '/profile/setting/:function',
component: () => import('$routes/profile/profile-functions.svelte'),
},
].map((routes) => {
return { ...routes, layout: ArrowBackLayout }
}),
{
path: '/error',
component: () => import('$routes/error.svelte'),
},
{
path: '/scanner',
component: () => import('$routes/qrscanner/qrscanner.svelte'),
},
/*{
path: '/test',
component: () => import('$routes/test.svelte'),
},*/
{
path: '/map',
component: () => import('$root/src/routes/map/map.svelte'),
},
{
path: '/profile/',
component: () => import('$src/__error.svelte'),
},
//policy
{
path: '/terms-and-conditions',
component: () => import('$routes/legal/terms-and-conditions.svelte'),
@ -89,10 +99,6 @@
{
path: '/cookie-policy',
component: () => import('$routes/legal/cookie-policy.svelte'),
},
{
path: '/forgot-pswd',
component: () => import('$routes/authorization/forgottonPassword/forgot-pswd.svelte'),
},
}
]}
/>

0
src/colors.css Normal file
View File

View File

@ -1,5 +1,10 @@
export type CheckPoint = {
$id: string
$collectionId: string
$createdAt: string
$databaseId: string
$permissions: string
$updatedAt: string
CPAfter: string
CPAnswerID: string
CPHint: string
@ -13,8 +18,14 @@ export type CheckPoint = {
}
export type Experience = {
$id: string
$collectionId: string
$createdAt: string
$databaseId: string
$permissions: string
$updatedAt: string
ExpApproved: boolean
ExpCpsID: string[]
ExpCPsID: string[]
ExpCategory?: string
ExpEnd0: string
ExpEnd60: string
@ -27,5 +38,6 @@ export type Experience = {
ExpTestingCode: string
ExpURL: string
UserID: string
checkPoint: Array<CheckPoint>
checkPoints: Array<CheckPoint>
rating: number
}

View File

@ -2,8 +2,16 @@ import { Collection } from './appwrite'
const experiences = new Collection('63cef30d6da945dd4250', '63cef4bd210fdf2e5888')
const users = new Collection('63ded6c18e8493bffc83', 'Users')
const interests = new Collection('6417cf1de159d094b370', '6417cf29f2118829b3b4')
const travel_with = new Collection('6417cf1de159d094b370', '6417d0429843609a2f49')
const recommended_by = new Collection('6417cf1de159d094b370', '6417d00e40701375978b')
const usersAnswers = new Collection('63cef30d6da945dd4250', 'users-answers')
export default {
experiences,
users,
interests,
travel_with,
recommended_by,
usersAnswers,
}

View File

@ -41,6 +41,11 @@
color: white;
background-color: rgb(66, 99, 235);
&.disabled {
&:hover {
background-color: rgb(107 114 128);
}
}
&:hover {
background-color: rgba(66, 99, 235, 0.8);
}

View File

@ -0,0 +1,55 @@
<!-- This component is imported to display a category to (de)select-->
<script lang="ts">
export let name;
export let image;
export let selected = false;
export let onToggle = () => {};
const handleClick = () => {
onToggle();
};
</script>
<div class="category" class:selected={selected} on:click={handleClick}>
<div class="image">
<img src={image} alt={name} />
</div>
<div class="name">{name}</div>
</div>
<style lang="scss">
.category {
display: flex;
flex-direction: column;
align-items: center;
width: 120px;
margin: 10px;
cursor: pointer;
.image {
width: 100%;
padding-top: 100%;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
}
}
.name {
margin-top: 5px;
font-size: 14px;
text-align: center;
}
&.selected {
border: 2px solid blue;
}
}
</style>

View File

@ -0,0 +1,17 @@
<script lang="ts">
export let preferences;
export let onPreferenceSelect;
</script>
<div class="category-2-in-row">
{#each preferences as preference}
<div
class="preference"
style="background-image: url({preference.img})"
on:click={() => onPreferenceSelect(preference)}
>
<span>{preference.name}</span>
</div>
{/each}
</div>

View File

@ -3,6 +3,7 @@
import collections from '$lib/collections'
import { Query } from 'appwrite'
import { navigate } from 'svelte-routing'
import Category from "$lib/components/Categories/category.svelte";
export let current_state = 1

View File

@ -0,0 +1,32 @@
<script lang="ts">
import { Img } from "flowbite-svelte"
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let name;
export let img;
</script>
<div class="preference">
<div
on:click={() => dispatch('selected')}
>
<Img alt="sample 1" size="max-w-lg" class="rounded-lg abcd" src={img}/>
<span>{name}</span>
</div>
</div>
<style lang="scss">
div
&.selected {
div {
border: 1px dashed #4263eb;
p {
color: #4263eb;
}
}
}
</style>

View File

@ -0,0 +1,190 @@
<script lang="ts">
import { each } from "svelte/internal"
import { Html } from "./Meta"
export let headline:string= "";
export let options:any[] = [];
export let on_return:Function = () => {};
export let default_ans:string = undefined
let selected:string = default_ans
function change_answer(){
const container = (event.currentTarget as HTMLInputElement).parentElement.parentElement
selected = (container.querySelector("input:checked") as HTMLInputElement).parentElement.querySelector("p").innerText
}
function cancel_popUp(){
on_return(undefined)
}
function save_popUp(){
on_return(selected)
}
</script>
<div class="pop-up-container">
<div class="pop-up">
<h1 class="headline">
{headline}
</h1>
<div class="options">
{#each options as option}
<label class="option">
{#if option === selected}
<input type="radio" on:change={change_answer} name="input" hidden checked/>
{:else}
<input type="radio" on:change={change_answer} name="input" hidden/>
{/if}
<div class="option-content">
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.26667 1.66671L6.10667 0.866707C6.04667 0.560041 5.77333 0.333374 5.45333 0.333374H0.666667C0.3 0.333374 0 0.633374 0 1.00004V11C0 11.3667 0.3 11.6667 0.666667 11.6667C1.03333 11.6667 1.33333 11.3667 1.33333 11V7.00004H5.06667L5.22667 7.80004C5.28667 8.11337 5.56 8.33337 5.88 8.33337H9.33333C9.7 8.33337 10 8.03337 10 7.66671V2.33337C10 1.96671 9.7 1.66671 9.33333 1.66671H6.26667Z" fill="white"/>
</svg>
<p>{option}</p>
</div>
</label>
{/each}
</div>
<div class="actions">
<button on:click={cancel_popUp} class="cancel">
Cancel
</button>
<button on:click={save_popUp} class="save">
Save
</button>
</div>
</div>
</div>
<style lang="scss">
.pop-up-container{
position: absolute;
z-index: 9999;
top: 0;
left: 0;
backdrop-filter: blur(5px) brightness(60%);
width: 100%;
height: 100%;
.pop-up{
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 24px;
gap: 16px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
background: #FFFFFF;
border-radius: 8px;
.headline{
font-family: 'Lato';
font-style: normal;
font-weight: 400;
font-size: 22px;
letter-spacing: 0.004em;
/* Black */
color: #212529;
margin: 0;
}
.options{
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap;
gap:16px;
.option{
.option-content{
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 8px 12px;
gap: 7px;
background: #F7F5FF;
svg, p{
color: #4263EB;
path{
fill: #4263EB
}
}
border-radius: 16px;
}
&:has(input:checked){
.option-content{
background:#4263EB;
svg, p{
color:#F7F5FF;
path{
fill: #F7F5FF
}
}
}
}
}
}
.actions{
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 16px;
button {
border-radius: 12px;
padding: 12px 16px;
text-align: center;
/* label medium */
font-family: 'Work Sans';
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
/* identical to box height, or 143% */
display: flex;
align-items: center;
letter-spacing: 0.4px;
&:hover{
opacity: 50%;
}
}
.save{
background: #4263EB;
color: white;
}
.cancel{
color: #4263EB;
background: #F7F5FF;
}
}
}
}
</style>

View File

@ -34,8 +34,8 @@
height: auto;
width: 100%;
overflow-y: auto;
position: absolute;
top: 25%;
position: relative;
top: -10%;
border-radius: 70px 70px 0 0;
padding: 52px;
display: flex;
@ -49,7 +49,7 @@
z-index: 4;
width: 100%;
height: auto;
position: absolute;
position: relative;
}
.shareButton {
width: 100%;

View File

@ -6,7 +6,6 @@
import FooterItem from '../Common/NavBar_Item.svelte'
$: [userInfo] = collections.users.getDocument([Query.equal('userId', $user?.$id || '')])
$: console.log($user)
$: items = [
{

View File

@ -1,21 +1,37 @@
<script>
<script lang="ts">
import GeolocateControl from '@beyonk/svelte-mapbox/src/lib/map/controls/GeolocateControl.svelte'
import Map from './Map.svelte'
import { navigate } from 'svelte-routing'
import NavigationBarLayout from '../Layouts/NavigationBarLayout.svelte'
import { createEventDispatcher, onMount } from 'svelte'
import LocationRequest from './LocationRequest.svelte'
const dispatch = createEventDispatcher()
export let center = null
export let mapComponent = null
export let user = { lat: null, lng: null }
export let userLocation = { lat: 0, lng: 0 }
$: console.log(userLocation, center)
let className = ''
export { className as class }
/*;(() => {
navigator.geolocation.getCurrentPosition(() => {
navigator.geolocation.watchPosition((e) => {
console.log(e)
})
})
})()*/
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition, () => dispatch('locationFailed'))
navigator.geolocation.getCurrentPosition((position: GeolocationPosition) => {
if (!userLocation) userLocation = { lat: position.coords.latitude, lng: position.coords.longitude }
if (!center) center = { lat: position.coords.latitude, lng: position.coords.longitude }
}) //at mapbox this is required dont know why
$: if (!center) center = userLocation
/* if (navigator.geolocation) {
navigator.geolocation.watchPosition(showPosition, () => dispatch('locationFailed'))
} else {
alert("Can't load your location!")
}
@ -23,19 +39,27 @@
user.lat = position.coords.latitude
user.lng = position.coords.longitude
if (!center) center = user
}
}*/
const userCenter = () => document.getElementsByClassName('mapboxgl-ctrl-geolocate')[0].click()
const userCenter = () => {
// @ts-ignore
document.getElementsByClassName('mapboxgl-ctrl-geolocate')[0].click()
}
</script>
<LocationRequest />
<NavigationBarLayout>
{#if center}
<Map on:ready={() => setTimeout(() => userCenter(), 40)} {center} bind:mapComponent class={className} on:move>
<Map on:ready={() => setTimeout(() => userCenter(), 100)} {center} bind:mapComponent class={className} on:move>
<slot />
<GeolocateControl
options={{ trackUserLocation: true }}
on:trackuserlocationstart={() => {}}
on:geolocate={(e) => {
// @ts-ignore
const { latitude, longitude } = e.detail.coords
user = { lat: latitude, lng: longitude }
userLocation = { lat: latitude, lng: longitude }
}}
/>
</Map>

View File

@ -13,15 +13,17 @@
</script>
{#if granted === false}
<Alert class="absolute w-[95%] max-w-[400px] z-50 mt-4" color="green">
<span class="text-lg font-medium">This is a info alert</span>
<div slot="extra">
<div class="mt-2 mb-4 text-sm">To advance through your experience you need to enable the location access, without it the app won't work.</div>
<div class="flex gap-2">
<Button color="green" on:click={() => (granted = true)} size="xs">ok</Button>
<div class="w-full justify-center flex absolute">
<Alert class="w-[95%] max-w-[400px] z-50 mt-4" color="green">
<span class="text-lg font-medium">This is a info alert</span>
<div slot="extra">
<div class="mt-2 mb-4 text-sm">To advance through your experience you need to enable the location access, without it the app won't work.</div>
<div class="flex gap-2">
<Button color="green" on:click={() => (granted = true)} size="xs">ok</Button>
</div>
</div>
</div>
</Alert>
</Alert>
</div>
{/if}
<!--{#if state === 'granted'}
<script>

View File

@ -10,7 +10,6 @@
export let radius = false
export let center: { lng: number; lat: number } = { lng: 0, lat: 0 }
$: console.log(center)
/*export const geo = (e) => {
geolocateControl.dispatchEvent('geolocate')
@ -37,6 +36,7 @@
mapComponent.setCenter([center.lng, center.lat], 14)
dispatch('ready')
}}
on:recentre={(e) => {}}
zoom={14}
>
<slot />

View File

@ -6,15 +6,15 @@
export let lat = 0
export let lng = 0
export let user = { lat: 0, lng: 0 }
export let userLocation = { lat: 0, lng: 0 }
export let popup: string | null = null
export let round = (1 / 110.574 / 1000) * 12 //cca 12m nutno pozměnit!!!!!!!!!! tento komentář nemazat
let Mlat = [lat - round, lat + round]
let Mlng = [lng - round, lng + round]
$: isIn = user ? user.lat > Mlat[0] && user.lat < Mlat[1] && user.lng > Mlng[0] && user.lng < Mlng[1] : null
$: isIn = userLocation ? userLocation.lat > Mlat[0] && userLocation.lat < Mlat[1] && userLocation.lng > Mlng[0] && userLocation.lng < Mlng[1] : null
$: isIn && dispatch('enter', { lat, lng, user })
$: isIn && dispatch('enter', { lat, lng, userLocation })
</script>
<Marker popup={false} {lat} {lng}>

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { Progressbar } from 'flowbite-svelte'
export let max: number
export let progress: number
export let showWrittenProgress = false
export let labelInside: boolean = false
$: value = JSON.stringify((progress / max) * 100)
let className = ''
export { className as class }
</script>
<div class={`w-full h-auto flex flex-wrap flex-row justify-center ${className}`}>
{#if showWrittenProgress}
<span>{progress}/{max}</span>
{/if}
<Progressbar {labelInside} progress={value} />
</div>

View File

@ -0,0 +1,12 @@
<script lang="ts">
import type { ComponentType, SvelteComponentTyped } from 'svelte'
export let path: string
export let component: () => Promise<any>
export let loading: ComponentType<SvelteComponentTyped<any>> | null = null
import { Route } from '$lib/router'
import LazyRouteGuard from './LazyRouteGuard.svelte'
</script>
<Route {path} let:location let:params>
<LazyRouteGuard {location} {params} {component} {loading} />
</Route>

View File

@ -0,0 +1,12 @@
<script lang="ts">
import type { RouteLocation } from 'svelte-routing/types/Route'
import Redirect from './Redirect.svelte'
export let fallback: string
export let allow: boolean
export let location: RouteLocation
</script>
{#if allow}
<slot />
{:else}
<Redirect to={fallback} replace state={{ from: location.pathname }} />
{/if}

View File

@ -33,3 +33,4 @@
{/if}
{/each}
</Router>

29
src/lib/router/routes.ts Normal file
View File

@ -0,0 +1,29 @@
import { ComponentType, SvelteComponentTyped } from 'svelte'
export interface Route {
component: () => Promise<any>,
path: string,
layout?: ComponentType<SvelteComponentTyped<any>>,
loading?: ComponentType<SvelteComponentTyped<any>>,
before?: () => any,
}
interface RouteDefinition {
component: () => Promise<any>,
path: string,
layout: ComponentType<SvelteComponentTyped<any>> | null,
loading: ComponentType<SvelteComponentTyped<any>> | null,
before: () => any | null,
}
interface RouteConfig {
routes: Route[],
layout?: ComponentType<SvelteComponentTyped<any>> | null,
loading?: ComponentType<SvelteComponentTyped<any>> | null,
}
const defineRoutes = (config: RouteConfig): RouteDefinition[] => {
return config.routes.map(route => ({ ...route, layout: route?.layout ?? config?.layout ?? null, loading: route?.loading ?? config?.loading ?? null, before: route?.before ?? null }))
}
export default defineRoutes

View File

@ -1,3 +1,3 @@
export const idGenerator = () => {
return JSON.stringify(Date.now() * Math.floor(Math.random() * 100000))
return JSON.stringify(Date.now() * Math.floor(Math.random() * 100000))
}

View File

@ -1,55 +0,0 @@
async function send({ method, path, body, token, headers }) {
const opts = { method, headers: new Headers(), mode: 'cors' }
opts.headers.append('Content-Type', 'application/json')
opts.headers.append('Access-Control-Allow-Origin', '*')
if (body) {
opts.body = JSON.stringify(body)
}
if (headers) {
for (const [k, v] of Object.entries(headers)) {
opts.headers.append(k, v)
}
}
if (token) {
opts.headers['Authorization'] = `Bearer ${token}`
}
const res = fetch(path, opts)
.then(async (r) => {
if (r.status >= 200 && r.status < 400) {
return await r.text()
} else {
return await r.text()
}
})
.then((str) => {
try {
return JSON.parse(str)
} catch (err) {
return str
}
})
return res
}
export function get(path, token = null) {
return send({ method: 'GET', path, body: null, token })
}
export function del(path, token = null) {
return send({ method: 'DELETE', path, body: null, token })
}
export function post(path, body, token = null) {
return send({ method: 'POST', path, body, token })
}
export function put(path, body, token = null, headers = []) {
return send({ method: 'PUT', path, body, token, headers })
}
export const hostName = 'https://erant.cz/api'

View File

@ -0,0 +1,148 @@
import { databases, user as userStore } from '$lib/appwrite'
import { Account, Models, Permission, Query, Role } from 'appwrite'
import database from 'svelte-appwrite-client/src/lib/database'
import { getLocationDataFromLatAndLong } from '../locations'
import { writable } from 'svelte/store'
import { CheckPoint, Experience } from '$lib/TStypes/experiences'
import collections from '$lib/collections'
export type AnswerState = 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
let user: { $id: string }
userStore.subscribe((res) => (user = res))
//Loading of checkpoints and rating is done in the same function to prevent multiple requests to the database
export const load = async (pathName: string, previewQuestionsCount?: number, preview?: Function) => {
const checkPoints: Array<CheckPoint> = []
// @ts-ignore
const experience: Experience = (await databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpURL', pathName)])).documents[0]
const checkPointsIds = experience.ExpCPsID
const rating = await getRating(experience.$id)
for (const checkPointId of checkPointsIds) {
try {
// @ts-ignore
checkPoints.push(await databases.getDocument('63cef30d6da945dd4250', '63cef84d908acf805758', checkPointId))
if (checkPointsIds.indexOf(checkPointId) === previewQuestionsCount - 1) {
experience['rating'] = rating
experience['checkPoints'] = checkPoints
preview(experience)
}
} catch (error) {}
}
experience['rating'] = rating
experience['checkPoints'] = checkPoints
return experience
}
//Fetch of Rating
const getRating = async (expId: string) => {
const { documents, total } = await databases.listDocuments('63cef30d6da945dd4250', '63ee6353ebb174cf815d', [Query.equal('ExpID', expId)])
const sum = documents.reduce((accumulator, rating) => accumulator + rating.ExpUserRating, 0)
return sum / total || 0
}
// Fetch of answers to the checkpoints
export const answer = async (experienceId: string, checkPointId: string, answer: any) => {
try {
const checkPoint = await databases.getDocument('63cef30d6da945dd4250', '63cef84d908acf805758', checkPointId)
console.log({ checkPoint })
const { CPType, CPAnswerID } = checkPoint
const correctAnswer = CPType !== 'INFO' ? (await databases.getDocument('63cef30d6da945dd4250', '63dd5c2b764061e40025', CPAnswerID)).CPAnswer : true
let correct: boolean = false
if (CPType === 'CHECKBOX') correct = JSON.stringify(answer) === JSON.stringify(correctAnswer)
if (CPType === 'RADIO' || CPType === 'NUMBER' || CPType === 'TEXT') correct = answer === correctAnswer[0]
if (CPType === 'INFO') correct = false
await saveAnswerIntoDatabase(experienceId, checkPointId, correct)
return correct
} catch (error) {
throw new Error(error)
//operation only for sveltekit 401, 500, 403
}
}
const saveAnswerIntoDatabase = async (experienceId: string, checkPointId: string, correct: boolean) => {
const previousAttemptDocument = await getUserAnswer(user.$id, checkPointId)
if (!(previousAttemptDocument?.attemptCount === 2)) {
if (previousAttemptDocument) {
collections.usersAnswers.updateDocument(previousAttemptDocument.$id, {
correct,
attemptCount: 2,
})
} else {
await collections.usersAnswers.createDocument(
{
userId: user.$id,
checkPoint: checkPointId,
correct,
experience: experienceId,
attemptCount: 1,
},
[Permission.read(Role.user(user.$id)), Permission.update(Role.user(user.$id))],
)
}
} else {
const err = new Error('User has already answered.')
err['code'] = '409'
throw err
}
}
export const getUserAnswer = async (userId, checkPointId) => {
return (await databases.listDocuments('63cef30d6da945dd4250', 'users-answers', [Query.equal('checkPoint', checkPointId), Query.equal('userId', userId)])).documents[0]
}
// Fetch of all experiences
export const getExperiences = async () => {
const experiences = (await databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpApproved', true)])).documents
let items = []
for (const experience of experiences) {
items.push({
city: (await getLocationDataFromLatAndLong(experience.ExpLocation[0], experience.ExpLocation[1])).city,
...experience,
})
}
return items
}
// Fetch of all experiences as store
export const getExperiencesAsStore = () => {
const store = writable([])
const loading = writable<boolean>(true)
databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpApproved', true)]).then(async ({ documents }) => {
let items: Array<any> = []
for (const experience of documents) {
items.push({
city: (await getLocationDataFromLatAndLong(experience.ExpLocation[0], experience.ExpLocation[1])).city,
...experience,
})
}
loading.set(false)
store.set(items)
})
return [store, loading] as const
}
export const getUserProgress = async (experienceId: string) => {
const { documents } = await databases.listDocuments('63cef30d6da945dd4250', 'users-answers', [Query.equal('userId', user.$id), Query.equal('experience', experienceId)])
return documents
}
export const getUserProgressAsStore = (experienceId: string) => {
const store = writable<Models.Document[]>([])
const loading = writable<boolean>(true)
getUserProgress(experienceId).then((documents) => {
store.set(documents)
loading.set(false)
})
return [store, loading] as const
}
// !pridat trideni podle kategorie! export const getExperiencesByCategory = async (category: string) => {}

View File

@ -1,76 +0,0 @@
import { databases } from '$lib/appwrite'
import { Query } from 'appwrite'
import database from 'svelte-appwrite-client/src/lib/database'
import { getLocationDataFromLatAndLong } from '../locations'
import { writable } from 'svelte/store'
import { Experience } from '$lib/TStypes/experiences'
export const load = async (pathName: string, previewQuestionsCount?: number, preview?: Function) => {
const checkPoints = []
const game = (await databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpURL', pathName)])).documents[0]
const checkPointsIds = game.ExpCPsID
const rating = await getRating(game.$id)
for (const checkPointId of checkPointsIds) {
try {
checkPoints.push(await databases.getDocument('63cef30d6da945dd4250', '63cef84d908acf805758', checkPointId))
if (checkPointsIds.indexOf(checkPointId) === previewQuestionsCount - 1) {
game['rating'] = rating
game['checkPoints'] = checkPoints
preview(game)
}
} catch (error) {}
}
game['rating'] = rating
game['checkPoints'] = checkPoints
return game
}
const getRating = async (expId: string) => {
const { documents, total } = await databases.listDocuments('63cef30d6da945dd4250', '63ee6353ebb174cf815d', [Query.equal('ExpID', expId)])
const sum = documents.reduce((accumulator, rating) => accumulator + rating.ExpUserRating, 0)
return sum / total || 0
}
export const answer = async (checkPointId: string, answer: any) => {
const checkPoint = await databases.getDocument('63cef30d6da945dd4250', '63cef84d908acf805758', checkPointId)
console.log({ checkPoint })
const { CPType, CPAnswerID } = checkPoint
const correctAnswer = (await databases.getDocument('63cef30d6da945dd4250', '63dd5c2b764061e40025', CPAnswerID)).CPAnswer
if (CPType === 'CHECKBOX') return JSON.stringify(answer) === JSON.stringify(correctAnswer)
if (CPType === 'RADIO' || CPType === 'NUMBER' || CPType === 'TEXT') return answer === correctAnswer[0]
}
export const getExpiriences = async () => {
const expiriences = (await databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpApproved', true)])).documents
let items = []
for (const expirience of expiriences) {
items.push({
city: (await getLocationDataFromLatAndLong(expirience.ExpLocation[0], expirience.ExpLocation[1])).city,
...expirience,
})
}
return items
}
export const getExpiriencesAsStore = () => {
const store = writable([])
const loading = writable<boolean>(true)
databases.listDocuments('63cef30d6da945dd4250', '63cef4bd210fdf2e5888', [Query.equal('ExpApproved', true)]).then(async ({ documents }) => {
let items: Array<any> = []
for (const expirience of documents) {
items.push({
city: (await getLocationDataFromLatAndLong(expirience.ExpLocation[0], expirience.ExpLocation[1])).city,
...expirience,
})
}
loading.set(false)
store.set(items)
})
return [store, loading] as const
}

View File

@ -0,0 +1,86 @@
import { databases } from '$lib/appwrite';
import { Query } from 'appwrite';
import { writable } from 'svelte/store'
// typescript interfaces for objects return definitions
interface Interest {
name: string;
img: string;
}
interface TravelBuddies {
name: string;
img: string;
}
interface Recommendations {
name: string;
img: string;
}
// Define a variable to store the selected preferences
// let selectedInterests = [];
// let selectedTravelBuddies = [];
// let selectedRecommendations = [];
// fetch interests
export const getInterests = () => {
const store = writable<Interest[]>([])
databases.listDocuments('6417cf1de159d094b370', '6417cf29f2118829b3b4').then(({ documents }) => {
// @ts-ignore
store.set(documents)
})
return [store] as const
}
// fetch travel buddies
export const getTravelBuddies = async (): Promise<TravelBuddies[]> => {
return ((await databases.listDocuments('6417cf1de159d094b370', '6417d0429843609a2f49')).documents as unknown as TravelBuddies[])};
// fetch recommendations
export const getRecommendations = async (): Promise<Recommendations[]> => {
return ((await databases.listDocuments('6417cf1de159d094b370', '6417d00e40701375978b')).documents as unknown as Recommendations[])};
// add selected preferences to user
export const addSelectedPreferences = async (userId, interests, travelBuddies, recommendations) => {
const { document: user } = await databases.getDocument('63ded6c18e8493bffc83', 'Users', userId);
const updatedUser = {
...user,
userInterests: interests,
userTravelBuddy: travelBuddies,
userRecommended: recommendations
};
await databases.updateDocument('63ded6c18e8493bffc83', 'Users', user.$id, updatedUser);
return updatedUser;
};
// // Function to update Interests
// function updateSelectedInterests(preference) {
// if (selectedInterests.includes(preference)) {
// selectedInterests = selectedInterests.filter((item) => item !== preference);
// } else {
// selectedInterests = [...selectedInterests, preference];
// }
// }
// // Function to update Travel Buddies
// function updateSelectedTravelBuddies(preference) {
// if (selectedTravelBuddies.includes(preference)) {
// selectedTravelBuddies = selectedTravelBuddies.filter((item) => item !== preference);
// } else {
// selectedTravelBuddies = [...selectedTravelBuddies, preference];
// }
// }
// // Function to update Recommendations
// function updateSelectedRecommendations(preference) {
// if (selectedRecommendations.includes(preference)) {
// selectedRecommendations = selectedRecommendations.filter((item) => item !== preference);
// } else {
// selectedRecommendations = [...selectedRecommendations, preference];
// }
// }

View File

@ -2,7 +2,7 @@ export const getLocationDataFromLatAndLong = async (latitude: number, longitude:
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json`)
const data = await response.json()
return {
city: data.address.town || data.address.city || data.address.village,
state: data.address.country,
city: data?.address?.town || data?.address?.city || data?.address?.village,
state: data?.address?.country,
}
}

View File

@ -1,15 +0,0 @@
export default (question, questionType) => {
switch (questionType.toLowerCase()) {
case 'text':
return question
case 'number':
return Number.parseFloat(question)
case 'choice':
return question.split(/;\s*/).map((item) => ({
label: item.startsWith('*') ? item.substring(1) : item,
value: item.startsWith('*') ? true : false,
}))
default:
return null
}
}

View File

@ -2,82 +2,82 @@ import { writable } from 'svelte/store'
import * as api from './api'
class FetchArray extends Array {
constructor(items, caller = () => null) {
super()
this.push(...items)
this.caller = caller
}
constructor (items, caller = () => null) {
super()
this.push(...items)
this.caller = caller
}
setFetch(caller) {
this.caller = caller
return this
}
setFetch (caller) {
this.caller = caller
return this
}
fetch(fetchBody = {}, then = () => null) {
return this.caller(fetchBody, then)
}
fetch (fetchBody = {}, then = () => null) {
return this.caller(fetchBody, then)
}
}
export function fetchable(path, initBody = {}) {
const { subscribe, set } = writable(null)
export function fetchable (path, initBody = {}) {
const { subscribe, set } = writable(null)
return {
subscribe,
fetch(fetchBody = {}, then = () => null) {
const body = Object.assign(initBody, fetchBody)
return {
subscribe,
fetch (fetchBody = {}, then = () => null) {
const body = Object.assign(initBody, fetchBody)
api.post(path, body).then((result) => {
set(result)
then(result)
})
api.post(path, body).then((result) => {
set(result)
then(result)
})
return this
}
}
return this
}
}
}
export function loadable(path, initBody = {}) {
const fetcher = fetchable(path, initBody)
const fetchCaller = fetcher.fetch
export function loadable (path, initBody = {}) {
const fetcher = fetchable(path, initBody)
const fetchCaller = fetcher.fetch
const isFetching = writable(false)
const isFetching = writable(false)
fetcher.fetch = (fetchBody = {}, then = () => null) => {
isFetching.set(true)
fetcher.fetch = (fetchBody = {}, then = () => null) => {
isFetching.set(true)
fetchCaller(fetchBody, (result) => {
isFetching.set(false)
then(result)
})
fetchCaller(fetchBody, (result) => {
isFetching.set(false)
then(result)
})
return new FetchArray([fetcher, isFetching], fetcher.fetch)
}
return new FetchArray([fetcher, isFetching], fetcher.fetch)
}
return new FetchArray([fetcher, isFetching], fetcher.fetch)
return new FetchArray([fetcher, isFetching], fetcher.fetch)
}
export function mutable(path, callback = () => null) {
const isFetching = writable(false)
const { subscribe, set } = writable(null)
export function mutable (path, callback = () => null) {
const isFetching = writable(false)
const { subscribe, set } = writable(null)
const mutateCall = async (fetchBody = {}) => {
isFetching.set(true)
const mutateCall = async (fetchBody = {}) => {
isFetching.set(true)
const result = await api.post(path, fetchBody)
set(result)
isFetching.set(false)
const result = await api.post(path, fetchBody)
set(result)
isFetching.set(false)
return result
}
return result
}
return [
{
subscribe,
mutate(fetchBody = {}) {
callback(mutateCall, fetchBody)
return this
}
},
isFetching
]
return [
{
subscribe,
mutate (fetchBody = {}) {
callback(mutateCall, fetchBody)
return this
}
},
isFetching
]
}

0
src/locales/cz.json Normal file
View File

13
src/locales/en.json Normal file
View File

@ -0,0 +1,13 @@
{
"page": {
"home": {
"title": "Discover the best Travel Experiences"
},
"Map": {
"title": "Map the best Travel Experiences"
},
"register": {
"title": "Register to the best Travel Experiences"
}
}
}

11
src/locales/i18n.ts Normal file
View File

@ -0,0 +1,11 @@
import { register, init, getLocaleFromNavigator, isLoading, locale, locales } from 'svelte-i18n'
import registers from './languages'
Object.entries(registers).forEach(([key, file]) => register(key, file))
export const i18n = () => init({
fallbackLocale: 'en',
initialLocale: getLocaleFromNavigator(),
})
export { isLoading, locale, locales }

4
src/locales/languages.ts Normal file
View File

@ -0,0 +1,4 @@
export default {
'en': () => import('./en.json'),
'cz': () => import('./cz.json')
}

View File

@ -2,7 +2,7 @@ import './app.css'
import App from './App.svelte'
const app = new App({
target: document.getElementById('app'),
target: document.getElementById('app')
})
export default app

View File

@ -9,6 +9,7 @@
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@500&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
html,
body {

View File

@ -7,6 +7,7 @@
import TopImage from '$lib/svg/Top-Image.svelte'
import { Helper } from 'flowbite-svelte'
import { getErrorMessage } from '../utils/authorizationErrors'
import Button from '$lib/components/Buttons/Button.svelte'
export let purpose = 'login' //possible values login, register
@ -58,13 +59,13 @@
<div class="forgot_password">
<a href="/forgot-password">Forgot password?</a>
</div>
<button class="loginButton" on:click={() => emailLogin()}>
<Button class="w-full text-2xl" primary disabled={password.length < 8} on:click={() => emailLogin()}>
{#if state === 'loading'}
<Loading class="text-white" />
{:else}
Sign in
Log in
{/if}
</button>
</Button>
</div>
</div>

View File

@ -11,6 +11,7 @@
erantId: params.erantId,
userName: $user.name,
userId: $user.$id,
termsAccepted: true,
},
[Permission.delete(Role.user($user.$id)), Permission.update(Role.user($user.$id)), Permission.read(Role.users('verified'))],
)

View File

@ -7,18 +7,19 @@
import { getErrorMessage } from '../utils/authorizationErrors'
import { getUserByErantId } from '$lib/utils/database/users'
import { ID } from 'appwrite'
import { Helper } from 'flowbite-svelte'
import { Checkbox, Helper } from 'flowbite-svelte'
import Button from '$lib/components/Buttons/Button.svelte'
let email = 'dfsafads'
let password = 'aaaaaaaaa'
let repeatPassword = 'aaaaaaaaa'
let name = 'fdsafsda'
let erantId = 'dfsafdsa'
let email = ''
let password = ''
let repeatPassword = ''
let name = ''
let erantId = ''
let termsChecked: boolean = false
let state: 'email-sent' | 'register' | 'loading' = 'register'
let error: string | null = null
$: buttonCodition = name.length > 0 && email.length > 0 && password.length >= 8 && password === repeatPassword && erantId.length > 2
$: buttonCodition = name.length > 0 && email.length > 0 && password.length >= 8 && password === repeatPassword && erantId.length > 2 && termsChecked
const register = async () => {
//if (password === repeatPassword || name.length < 8 || email.length < 8) throw new Error('conditions are not fine')
@ -26,15 +27,18 @@
state = 'loading'
error = null
if (await getUserByErantId(erantId)) {
const err = new Error('fdjsaůl')
const err = new Error('Erant ID is already taken')
err['code'] = 1001
throw err
}
await account.create(ID.unique(), email, password, name)
await account.createEmailSession(email, password)
await account.createVerification(`${location.origin}/register/emailverification/${erantId}`)
state = 'email-sent'
} catch (err) {
console.log(err)
error = getErrorMessage(err.code)
state = 'register'
}
@ -54,18 +58,20 @@
<Helper class="ml-4" color="red">{error}</Helper>
{/if}
<div class="inform">
<input bind:value={name} type="text" placeholder="Name" autocomplete="full-name" required />
<input bind:value={email} type="text" placeholder="E-mail" autocomplete="email" required />
<input bind:value={name} type="text" placeholder="Your name" autocomplete="full-name" required />
<input bind:value={email} type="text" placeholder="Your e-mail" autocomplete="email" required />
<input bind:value={erantId} type="text" placeholder="@your_nickname" autocomplete="email" required />
<HiddenInput bind:value={password} placeholder="Password" />
<HiddenInput bind:value={repeatPassword} placeholder="Re-type password" />
<div class="w-full">
<HiddenInput bind:value={repeatPassword} placeholder="Re-type password" />
{#if password !== repeatPassword && password.length >= 8}
<Helper class="flex justify-start w-full pl-4" helperClass="text-sm" color="red">Passwords are not equal</Helper>
{/if}
</div>
<div class="flex items-center">
<input
id="link-checkbox"
type="checkbox"
value=""
<Checkbox
bind:checked={termsChecked}
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
required
/>
<label for="link-checkbox" class="ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>I agree with the <a href="erant.cz/terms-and-conditions" class="text-blue-600 dark:text-blue-500 hover:underline">terms and conditions</a>,

View File

@ -2,7 +2,7 @@ export const errors = {
1001: 'Nickname is already taken',
409: 'A user with the same email already is in Erant',
401: 'Invalid credentials.',
400: 'Password must be at least 8 characters.',
400: 'Email musts be valid.',
}
export const getErrorMessage = (code: number) => {

View File

@ -1,10 +1,9 @@
<script>
import Overlay from '../lib/components/Layouts/LayoutImg.svelte'
import Section from '../lib/components/Common/Section.svelte'
import Button from '../lib/components/Buttons/Button.svelte'
import Overlay from '$lib/components/Layouts/LayoutImg.svelte'
import Section from '$lib/components/Common/Section.svelte'
import Button from '$lib/components/Buttons/Button.svelte'
</script>
<Overlay img="/assets/images/main.jpg">
<div>
<span>Erant</span>

View File

@ -2,24 +2,24 @@
import NavigationBarLayout from '../../lib/components/Layouts/NavigationBarLayout.svelte'
import Result from './Components/Result.svelte'
import Top from './Components/Top.svelte'
import { getExpiriences } from '$lib/utils/database/game'
import { getExperiences } from '$lib/utils/database/experience'
import Loading from '$lib/components/Common/Loading.svelte'
import Comparment from '../homepage/Components/Comparment.svelte'
let Search: string
//
</script>
<div class="content">
<Top bind:search_value={Search} />
<div class="results">
{#await getExpiriences()}
{#await getExperiences()}
<div class="w-full h-24 flex items-center justify-center">
<Loading />
</div>
{:then expiriences}
{#each expiriences as result}
<Result name={result.name} location={result.location} link={result.link} />
{/each}
<Comparment direction="col" items={expiriences} />
{/await}
<div class="end" />

View File

@ -1,54 +1,63 @@
<script lang="ts">
import Section from '../../../lib/components/Common/Section.svelte'
import Button from '../../../lib/components/Buttons/Button.svelte'
import Image from '../../../lib/components/Common/Image.svelte'
import Section from '$lib/components/Common/Section.svelte'
import Button from '$lib/components/Buttons/Button.svelte'
import Image from '$lib/components/Common/Image.svelte'
import { createEventDispatcher } from 'svelte'
import Loading from '$lib/components/Common/Loading.svelte'
const dispatch = createEventDispatcher()
export let control: 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
export let nextQuestion
export let imgSrc
export let imgSrc: string = ''
export let loading: boolean
//
</script>
<div>
<div class="wrap">
<div class="h-[var(--quizHeader)] w-full flex relative top-0 justify-center items-center">
<div class="flex justify-center items-center flex-col flex-wrap gap-3">
<span class="title"><slot name="title" /></span>
<span style="width: 100%"><Image class="w-full h-[200px]" src={imgSrc} /></span>
<div class="wrap mt-2">
<div class="w-full flex relative top-0 justify-center items-center">
<div class="flex w-full justify-center items-center flex-col flex-wrap gap-3">
<span class="title h-min"><slot name="title" /></span>
{#if imgSrc}
<span style="width: 100%"><Image class="w-full h-[200px]" src={imgSrc} /></span>
{/if}
</div>
</div>
<Section style="height: calc(100% - var(--quizHeader)); position: relative">
<div class="flex items-center justify-center flex-col w-full gap-24 p-4">
<div class="popis">
<span> Popis úkolu: </span>
<span> <slot name="about" /></span>
</div>
{#if control === 'wrong-firstTime' || control === 'wrong-secondTime'}
<div class="popis">
<span> Nápověda: </span>
<span> <slot name="hint" /></span>
</div>
{/if}
<div class="flex items-center justify-center flex-col w-full gap-6">
<span class="relative"> <slot name="answers" /> </span>
<div class="w-full relative flex justify-center h-fit size">
{#if control === 'not-control' || control === null}
<span />
{:else if control === 'wrong-secondTime' || control === 'correct'}
<div class="flex w-auto h-auto flex-wrap flex-col items-center gap-2">
<span class={`${control === 'correct' ? 'text-green-500' : 'text-red-500'}`}>{control === 'correct' ? 'správně' : 'špatně'}</span>
<span> <slot name="after" /></span>
</div>
{:else if control === 'wrong-firstTime'}
<div class="flex w-auto h-auto flex-wrap flex-col items-center gap-2">
<span class="text-red-500">To není správně, zkus to znovu</span>
<span> <slot name="hint" /></span>
</div>
{/if}
</div>
{#if control === 'not-control' || control === 'correct' || control === 'wrong-secondTime'}
<Button on:submit={nextQuestion} primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">Na další otázku</Button>
<Button on:submit={() => dispatch('nextQuestion')} primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">Na další otázku</Button>
{:else if control === 'wrong-firstTime' || control === null}
<Button on:submit primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">Vyhodnotit</Button>
<Button disabled={loading} on:submit primary class="w-3/4 max-w-sm min-w-[400px] h-16 bottom-0 m-10 relative">
{#if loading}
<Loading />
{:else}
Vyhodnotit
{/if}
</Button>
{/if}
</div>
</div>
<div class="w-full relative bottom-[170px] flex justify-center h-fit size">
{#if control === 'not-control' || control === null}
<span />
{:else if control === 'correct'}
<span style="color:greenyellow">správně</span>
{:else if control === 'wrong-firstTime'}
<span style="color:red">druhypokus</span>
{:else if control === 'wrong-secondTime'}
<span style="color:red">špatně</span>
{/if}
</div>
</Section>
</div>
</div>

View File

@ -1,12 +1,28 @@
<script>
<script lang="ts">
import LayoutImg from '../../../lib/components/Layouts/LayoutImg.svelte'
import Button from '../../../lib/components/Buttons/Button.svelte'
export let img
import { Experience } from '$lib/TStypes/experiences'
import { navigate } from '$lib/router'
export let gameData: Experience
export let client
let score = (client.points / client.possiblePointsToSeize) * 100
console.log(score)
</script>
<LayoutImg {img}>
<div class="w-full h-full flex flex-wrap flex-row gap-4 justify-center">
<div class="h-full w-full flex justify-self-center justify-center text-[32px]"><slot /></div>
<Button class="w-80 absolute bottom-0 mb-6" href="/">ukončit hru</Button>
<LayoutImg img={gameData.ExpImage}>
<div class="w-full h-auto flex flex-wrap flex-row gap-4 justify-center">
<div class="h-full w-full flex justify-self-center justify-center text-[32px] flex-wrap flex-col gap-4 items-center">
<span> Získali jste {client.points} / {client.possiblePointsToSeize} bodů</span>
<span>
{#if score > 90}
{@html gameData.ExpEnd100}
{:else if score > 50}
{@html gameData.ExpEnd60}
{:else}
{@html gameData.ExpEnd0}
{/if}
</span>
</div>
<Button class="w-80 mt-8 " on:click={() => navigate(-1)}>ukončit hru</Button>
</div>
</LayoutImg>

View File

@ -1,23 +1,4 @@
<script lang="ts">
import Layout from '../Components/Layout.svelte'
import { CheckPoint } from '$lib/TStypes/experiences'
export let data: { name: string; checkPoint: CheckPoint }
const { checkPoint, name } = data
export let nextQuestion
</script>
<Layout
imgSrc={'gameData.question.thumbnail'}
control={'not-control'}
nextQuestion={() => {
nextQuestion()
}}
>
<span slot="title">{name}</span>
<span slot="about">{@html checkPoint.CPText}</span>
</Layout>
<style lang="scss">
</style>
<div />

View File

@ -2,45 +2,22 @@
import Layout from '../Components/Layout.svelte'
import CheckBox from '../../../lib/components/Inputs/Checkbox.svelte'
import { CheckPoint } from '$lib/TStypes/experiences'
import { answer } from '$lib/utils/database/game'
import { answer } from '$lib/utils/database/experience'
export let checkPoint: CheckPoint
export let myAnswer
export let clear: false | true = false
$: if (clear) myAnswers = new Array(checkPoint.CPOptions.length).fill(false)
export let data: { name: string; checkPoint: CheckPoint }
const { checkPoint, name } = data
let myAnswers = new Array(checkPoint.CPOptions.length).fill(false)
export let control: 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
export let nextQuestion
export let attempt: 1 | 2 = 1
const answerCheckBox = async () => {
const arr = checkPoint.CPOptions.filter((item, i) => {
if (myAnswers[i] === true) return item
})
const res = await answer(checkPoint.$id, arr)
if (res) control = 'correct'
else if (attempt === 1) control = 'wrong-firstTime'
else control = 'wrong-secondTime'
attempt++
}
$: myAnswer = checkPoint.CPOptions.filter((item, i) => {
if (myAnswers[i] === true) return item
})
</script>
<Layout
imgSrc={'gameData.question.thumbnail'}
nextQuestion={() => {
nextQuestion()
myAnswers = new Array(checkPoint.CPOptions.length).fill(false)
}}
{control}
on:submit={async () => answerCheckBox()}
>
<span slot="title">{name}</span>
<span slot="about">{@html checkPoint.CPText}</span>
<span slot="hint"> {@html checkPoint.CPHint} </span>
<div slot="answers">
{#each checkPoint.CPOptions as label, i}
<span class="self-baseline">
<CheckBox bind:checked={myAnswers[i]}>{label}</CheckBox>
</span>
{/each}
</div>
</Layout>
{#each checkPoint.CPOptions as label, i}
<span class="self-baseline">
<CheckBox bind:checked={myAnswers[i]}>{label}</CheckBox>
</span>
{/each}

View File

@ -1,47 +1,11 @@
<script lang="ts">
import Layout from '../Components/Layout.svelte'
import Input from '../../../lib/components/Inputs/Input.svelte'
import { CheckPoint } from '$lib/TStypes/experiences'
import { answer } from '$lib/utils/database/game'
import { answer } from '$lib/utils/database/experience'
export let data: { name: string; checkPoint: CheckPoint }
const { checkPoint, name } = data
let myAnswer = ''
export let control: 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
export let nextQuestion
export let attempt: 1 | 2 = 1
const answer_ = async () => {
const res = await answer(checkPoint.$id, myAnswer)
if (res) control = 'correct'
else if (attempt === 1) control = 'wrong-firstTime'
else control = 'wrong-secondTime'
attempt++
}
export let myAnswer = ''
export let clear: false | true = false
$: if (clear) myAnswer = ''
</script>
<Layout
imgSrc={'gameData.question.thumbnail'}
nextQuestion={() => {
nextQuestion()
myAnswer = ''
}}
{control}
on:submit={() => (myAnswer !== '' ? answer_() : null)}
>
<span slot="title">{name}</span>
<span slot="about">{@html checkPoint.CPText}</span>
<span slot="hint"> {@html checkPoint.CPHint} </span>
<div slot="answers">
<span class="self-baseline">
<Input type="number" bind:value={myAnswer} class="w-full min-w-[400px] max-w-[500px] h-12" />
</span>
</div>
</Layout>
<style lang="scss">
/*.input::-webkit-inner-spin-button {
background: blue;
}*/
</style>
<Input type="number" bind:value={myAnswer} class="w-full min-w-[400px] max-w-[500px] h-12" />

View File

@ -1,5 +1,4 @@
<script lang="ts">
import parseQuestion from '../../../lib/utils/parseQuestion'
import Marker from '../../../lib/components/Map/Marker.svelte'
import TextForm from './TextForm.svelte'
import NumberForm from './NumberForm.svelte'
@ -8,9 +7,17 @@
import IntervalForm from './IntervalForm.svelte'
import QrCode from './QrCode.svelte'
import Finish from './Finish.svelte'
import { data } from '$lib/stores/game'
import { data } from '$lib/stores/stores'
import Erantmap from '$lib/components/Map/Erantmap.svelte'
import Info from './Info.svelte'
import { Experience } from '$lib/TStypes/experiences'
import Layout from '../Components/Layout.svelte'
import { answer, AnswerState, getUserAnswer, getUserProgressAsStore } from '$lib/utils/database/experience'
import { user } from '$lib/appwrite'
import Button from '$lib/components/Buttons/Button.svelte'
import LayoutImg from '$lib/components/Layouts/LayoutImg.svelte'
import { Models } from 'appwrite'
import { Writable } from 'svelte/store'
const components = {
TEXT: TextForm,
@ -22,80 +29,127 @@
INFO: Info,
}
export let control: 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null = null
let view: 'question' | 'map' | 'end' = 'map'
export let control: AnswerState = null
let view: 'question' | 'map' | 'end' | 'start' | 'start-map' = 'start-map'
export let gameData: Experience //data
$: [userProgress, userProgressLoading] = gameData ? getUserProgressAsStore(gameData?.$id) : []
export let gameData: any = {} //data
$: console.log(gameData)
let clientAnswers = {
let client = {
//user data about game
pos: 0,
end: gameData.checkPoints.length - 1, //kolik otázek
end: gameData.ExpCPsID.length - 1, //kolik otázek
points: 0, //body
possiblePointsToSeize: gameData.checkPoints.length * 2,
}
$: if (client.pos < $userProgress?.length - 1 + 1) {
// nastaví na continue
client.pos = $userProgress?.length - 1 + 1
view = 'map'
}
$: console.log(clientAnswers.pos === clientAnswers.end + 1)
$: if (gameData.checkPoints[client.pos].CPType === 'INFO' && !$userProgressLoading) control = 'not-control'
$: if (control === 'correct') clientAnswers.points += 2 //body bodování
$: console.log(control)
const nextQuestion = () => {
//další otázka
console.log({ control })
control = null
if (clientAnswers.pos === clientAnswers.end) view = 'end'
if (client.pos === client.end) view = 'end'
else {
clientAnswers.pos++
client.pos++
view = 'map'
}
}
//let answers
//$: if (clientAnswers.pos < clientAnswers.end) answers = parseQuestion(gameData.questions[clientAnswers.pos].answer, gameData.questions[clientAnswers.pos].type) //delete
$: checkPoint = gameData.checkPoints[client.pos]
$: checkPointType = checkPoint.CPType
let page = null
$: page = view === 'question' ? components[gameData.checkPoints[clientAnswers.pos].CPType] : null
$: page = view === 'question' ? components[checkPointType] : null
let [lat, lng] = [null, null]
$: if (clientAnswers.pos < clientAnswers.end) [lat, lng] = $data.checkPoints[clientAnswers.pos].CPLocation
$: if (client.pos < client.end) [lat, lng] = view === 'map' ? gameData.checkPoints[client.pos].CPLocation : gameData.ExpLocation
let user = { lat: null, lng: null }
let userLocation = { lat: 0, lng: 0 }
$: console.log(gameData.checkPoints[clientAnswers.pos].CPType, clientAnswers.pos, view)
let myAnswer: string | string[]
let clear: boolean = false
let answerLoading = false
/* //set user to localstorage
$: if (clientAnswers.pos !== clientAnswers.end && clientAnswers.pos !== 0) {
//nastaví
localStorage.setItem(`/${gameData.url}`, JSON.stringify(clientAnswers.pos))
localStorage.setItem('lastGame', `/${gameData.url}`)
} else {
//vymaže když jsi dohrál
localStorage.removeItem(`/${gameData.url}`)
localStorage.removeItem('lastGame')
const checkAnswer = async () => {
answerLoading = true
try {
const res = await answer(gameData.$id, checkPoint.$id, myAnswer)
if (checkPointType === 'INFO') {
nextQuestion()
} else {
if (res) {
control = 'correct'
client.points += 2
} else if (control === null) {
control = 'wrong-firstTime'
clear = true
} else control = 'wrong-secondTime'
setTimeout(() => (clear = false), 400)
}
} catch (error) {
console.log(error)
}
answerLoading = false
}
//is user already in game
const userInGame = JSON.parse(localStorage.getItem(location.pathname))
if (userInGame) {
clientAnswers.pos = userInGame
}*/
</script>
<input type="number" bind:value={clientAnswers.pos} />
<button on:click={() => (view = 'question')}>disappear map</button>
<input type="number" bind:value={client.pos} />
<button on:click={() => (view = view === 'start-map' ? 'start' : 'question')}>disappear map</button>
{#if view === 'map'}
<Erantmap bind:user class="w-full h-full">
<Marker on:enter={() => (view = 'question')} {lat} {lng} {user} />
{#if view === 'map' || view === 'start-map'}
<Erantmap bind:userLocation class="w-full h-full">
<Marker on:enter={() => (view = view === 'start-map' ? 'start' : 'question')} {lat} {lng} {userLocation} />
</Erantmap>
{/if}
{#if view === 'start'}
<LayoutImg img={gameData.ExpImage}>
<div class="w-full h-auto flex flex-wrap flex-row gap-4 justify-center">
<div class="h-full w-full flex justify-self-center justify-center text-[32px] flex-wrap flex-col gap-4 items-center">
{@html gameData.ExpStart}
</div>
<Button class="w-80 mt-8 " on:click={() => (view = 'map')}>pokračovat</Button>
</div>
</LayoutImg>
{/if}
{#if view === 'question'}
<svelte:component this={page} data={{ checkPoint: gameData.checkPoints[clientAnswers.pos], name: gameData.ExpName }} {nextQuestion} bind:control />
<Layout
loading={answerLoading}
imgSrc={checkPoint.CPImage}
on:submit={() => checkAnswer()}
on:nextQuestion={() => {
if (checkPointType === 'INFO') checkAnswer()
else nextQuestion()
}}
{control}
>
<span slot="title">{checkPoint.CPName}</span>
<span slot="about">{@html checkPoint.CPText}</span>
<span slot="hint">
{#if checkPoint.CPHint}
{@html checkPoint.CPHint}
{/if}
</span>
<span slot="after">
{#if checkPoint.CPAfter}
{@html checkPoint.CPAfter}
{/if}
</span>
<div slot="answers">
<span class="self-baseline">
<svelte:component this={page} {clear} {checkPoint} bind:myAnswer />
</span>
</div>
</Layout>
{/if}
{#if view === 'end'}
<Finish img={gameData.thumbnail}>
<span> Získali jste {clientAnswers.points} / {clientAnswers.end * 2} bodů</span>
</Finish>
<Finish {client} {gameData} />
{/if}

View File

@ -2,44 +2,17 @@
import Radio from '../../../lib/components/Inputs/Radio.svelte'
import Layout from '../Components/Layout.svelte'
import { CheckPoint } from '$lib/TStypes/experiences'
import { answer } from '$lib/utils/database/game'
import { answer } from '$lib/utils/database/experience'
export let data: { name: string; checkPoint: CheckPoint }
const { checkPoint, name } = data
let myAnswer = ''
export let checkPoint: CheckPoint
export let myAnswer = ''
export let control: 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
export let nextQuestion
export let attempt: 1 | 2 = 1
const answer_ = async () => {
const res = await answer(checkPoint.$id, myAnswer)
if (res) control = 'correct'
else if (attempt === 1) control = 'wrong-firstTime'
else control = 'wrong-secondTime'
attempt++
}
export let clear: false | true = false
$: if (clear) myAnswer = ''
</script>
<Layout
imgSrc={'gameData.question.thumbnail'}
{control}
nextQuestion={() => {
nextQuestion()
myAnswer = ''
}}
on:submit={() => (myAnswer !== '' ? answer_() : null)}
>
<span slot="title">{name}</span>
<span slot="about">{@html checkPoint.CPText}</span>
<div slot="answers">
{#each checkPoint.CPOptions as label}
<span class="self-baseline">
<Radio value={label} bind:group={myAnswer}>{label}</Radio>
</span>
{/each}
</div>
</Layout>
<style lang="scss">
</style>
{#each checkPoint.CPOptions as label}
<span class="self-baseline">
<Radio value={label} bind:group={myAnswer}>{label}</Radio>
</span>
{/each}

View File

@ -2,43 +2,11 @@
import Layout from '../Components/Layout.svelte'
import Input from '../../../lib/components/Inputs/Input.svelte'
import { CheckPoint } from '$lib/TStypes/experiences'
import { answer } from '$lib/utils/database/game'
import { answer } from '$lib/utils/database/experience'
export let data: { name: string; checkPoint: CheckPoint }
const { checkPoint, name } = data
let myAnswer = ''
export let control: 'wrong-firstTime' | 'wrong-secondTime' | 'correct' | 'not-control' | null
export let nextQuestion
export let attempt: 1 | 2 = 1
const answer_ = async () => {
const res = await answer(checkPoint.$id, myAnswer)
if (res) control = 'correct'
else if (attempt === 1) control = 'wrong-firstTime'
else control = 'wrong-secondTime'
attempt++
}
export let myAnswer = ''
export let clear: false | true = false
$: if (clear) myAnswer = ''
</script>
<Layout
imgSrc={'gameData.question.thumbnail'}
{control}
nextQuestion={() => {
nextQuestion()
myAnswer = ''
}}
on:submit={() => (myAnswer !== '' ? answer_() : null)}
>
<span slot="title">{name}</span>
<span slot="about">{@html checkPoint.CPText}</span>
<span slot="hint"> {@html checkPoint.CPHint} </span>
<div slot="answers">
<span class="self-baseline">
<Input type="text" bind:value={myAnswer} class="w-full min-w-[400px] max-w-[500px] h-12" />
</span>
</div>
</Layout>
<style lang="scss">
</style>
<Input type="text" bind:value={myAnswer} class="w-full min-w-[400px] max-w-[500px] h-12" />

View File

@ -6,74 +6,72 @@
import IconStar from '../../lib/svg/Star.svelte'
import IconPoint from '../../lib/svg/Point.svelte'
import Loading from '../../lib/components/Common/Loading.svelte'
import { data } from '../../lib/stores/game'
import { data } from '../../lib/stores/stores'
import GeolocateControl from '@beyonk/svelte-mapbox/src/lib/map/controls/GeolocateControl.svelte'
import Map from '../../lib/components/Map/Map.svelte'
import Renderer from './Forms/Renderer.svelte'
import Redirect from '../../lib/components/Common/Redirect.svelte'
import ImageSlider from '../../lib/components/Common/ImageSlider.svelte'
import Marker from '../../lib/components/Map/Marker.svelte'
import collections from '$lib/collections'
import { Query } from 'appwrite'
import { onMount } from 'svelte'
import { load } from '$lib/utils/database/game'
import { getUserProgressAsStore, load } from '$lib/utils/database/experience'
import { getLocationDataFromLatAndLong } from '$lib/utils/locations'
import { navigate } from '$lib/router'
import { Experience } from '$lib/TStypes/experiences'
import Progressbar from '$lib/components/erant/Progressbar.svelte'
export let params: { gameurl: string }
let gameData: Experience //
$: [userProgress] = gameData ? getUserProgressAsStore(gameData?.$id) : []
onMount(async () => {
try {
$data = await load(params.gameurl, 5, (preview) => {
$data = preview
view = 'game-preview'
gameData = await load(params.gameurl, 2, (preview) => {
gameData = preview
view = 'experience-preview'
})
view = 'game-preview'
} catch (error) {
navigate('/error')
}
})
$: console.log($data)
$: assets = $data?.questions
/*$: assets = gameData?.questions
?.filter((q) => q.thumbnail !== null)
?.slice(0, 8)
?.map((q) => q.thumbnail)
?.map((q) => q.thumbnail)*/
let view: 'game-play' | 'game-loading' | 'game-preview' = 'game-loading'
let view: 'experience-play' | 'experience-loading' | 'experience-preview' = 'experience-loading'
//is user already in game
//const userInGame = JSON.parse(localStorage.getItem(location.pathname))
//$: if ($data && userInGame) view = 'game-play'
//$: if (gameData && userInGame) view = 'game-play'
</script>
{#if view === 'game-loading'}
{#if view === 'experience-loading'}
<h1 class="flex items-center justify-center flex-col">
<span>Experience is loading...</span>
<Loading />
</h1>
{:else if view === 'game-preview'}
<Overlay shareData={{ url: window.location.href }} img={$data.ExpImage}>
{:else if view === 'experience-preview'}
<Overlay shareData={{ url: window.location.href }} img={gameData.ExpImage}>
<div>
<span class="text-[36px]">{$data.ExpName}</span>
<span class="text-[36px]">{gameData.ExpName}</span>
<div class="bubbles">
<Bubble background="blue">
<span slot="icon"><IconStar /></span>
<span> {$data.rating} </span>
<span> {gameData.rating} </span>
</Bubble>
<Bubble background="white">
<span slot="icon"><IconPoint /></span>
{#await getLocationDataFromLatAndLong($data.ExpLocation[0], $data.ExpLocation[1]) then { city }}
{#await getLocationDataFromLatAndLong(gameData.ExpLocation[0], gameData.ExpLocation[1]) then { city }}
<span>{city}</span>
{/await}
</Bubble>
</div>
</div>
{#if $data.ExpIntroduction}
{#if gameData.ExpIntroduction}
<Section title="Popis">
<span>
{@html $data.ExpIntroduction}
{@html gameData.ExpIntroduction}
</span>
</Section>
{/if}
@ -85,23 +83,26 @@
</div>
</div>
</Section>
<Section title="Progress">
<Progressbar max={gameData.ExpCPsID.length} progress={$userProgress.length} showWrittenProgress />
</Section>
<div class="w-full relative">
<div class="px-4 m-auto" style="max-width: var(--max-viewport-width);">
<Map radius class="w-full h-44" center={{ lng: $data.ExpLocation[1], lat: $data.ExpLocation[0] }}>
{#each $data.checkPoints as { CPLocation: [lat, lng] }}
<Map radius class="w-full h-44" center={{ lng: gameData.ExpLocation[1], lat: gameData.ExpLocation[0] }}>
{#each gameData.checkPoints as { CPLocation: [lat, lng] }}
<Marker {lat} {lng} />
{/each}
</Map>
</div>
</div>
{#if $data.checkPoints.length}
<Button on:click={() => (view = 'game-play')} primary>Hrát</Button>
{#if gameData.checkPoints.length}
<Button on:click={() => (view = 'experience-play')} primary>{$userProgress ? 'Pokračovat' : 'Hrát'}</Button>
{/if}
</Overlay>
{:else if view === 'game-play'}
<Renderer gameData={$data} />
{:else if view === 'experience-play'}
<Renderer {gameData} />
{/if}
<style>

View File

@ -1,19 +1,18 @@
<script>
<script lang="ts">
import { navigate } from '$lib/router'
import CompartmentItem from './Compartment_Item.svelte'
import Headline from './Headline.svelte'
export let items = []
console.log(items)
export let direction: 'col' | 'row' = 'row'
</script>
<div class="section">
<Headline content="Inspiration on your trip" />
<div class="options">
<div class={`options ${direction === 'row' ? 'flex-row' : 'flex-col'}`}>
{#each items as item}
<CompartmentItem on:click={() => navigate(`${item.ExpURL}`)} price={item.price} location={item.location} name={item.ExpName} image={item.ExpImage} />
<CompartmentItem on:click={() => navigate(`${item.ExpURL}`)} city={item.city} name={item.ExpName} image={item.ExpImage} />
{/each}
</div>
</div>
@ -36,7 +35,6 @@
.section > .options {
display: flex;
flex-direction: row;
gap: 12px;
align-items: flex-start;

View File

@ -1,5 +1,5 @@
<script>
export let location = 'Home'
export let city = 'Home'
export let name = 'Kawa Ijen'
export let image = ''
</script>
@ -49,7 +49,7 @@
</g>
</svg>
{location}
{city}
</div>
</div>
</button>

View File

@ -1,12 +1,13 @@
<script>
import NavigationBarLayout from '../../lib/components/Layouts/NavigationBarLayout.svelte'
import { Img } from "flowbite-svelte"
import Discover from './Components/Discover.svelte'
import Top from './Components/Top.svelte'
import Categories from './Components/Categories.svelte'
import Comparment from './Components/Comparment.svelte' //do budoucna bych to udělal pomocí komponent
import { onMount } from 'svelte'
import Animation from './Components/Animation.svelte'
import { getExpiriences } from '$lib/utils/database/game'
import { getExperiences } from '$lib/utils/database/experience'
import Loading from '$lib/components/Common/Loading.svelte'
let fitstTime = false
@ -35,7 +36,6 @@
})
}
</script>
{#if !fitstTime}
<Top headline={city + state} />
@ -45,12 +45,12 @@
<Categories />
{#await getExpiriences()}
{#await getExperiences()}
<div class="w-full h-24 flex items-center justify-center">
<Loading />
</div>
{:then expiriences}
<Comparment items={expiriences} />
{:then experiences}
<Comparment items={experiences} />
{/await}
{:else}
<div class="content">

View File

@ -4,7 +4,7 @@
import LocationRequest from '$lib/components/Map/LocationRequest.svelte'
import Marker from '$lib/components/Map/Marker.svelte'
import { navigate } from '$lib/router'
import { getExpiriencesAsStore } from '$lib/utils/database/game'
import { getExperiencesAsStore } from '$lib/utils/database/experience'
import { onMount } from 'svelte'
let user: { lat: number; lng: number } = { lat: 0, lng: 0 }
@ -19,7 +19,7 @@
navigator.geolocation.getCurrentPosition(handleLocationGranted)
$: [experiences] = getExpiriencesAsStore()
$: [experiences] = getExperiencesAsStore()
</script>
<NavigationBarLayout>

View File

@ -0,0 +1,66 @@
<script lang="ts">
import Interests from './../../lib/components/Categories/interests.svelte';
// Import necessary modules and components
import { user } from '$lib/appwrite';
import collections from '$lib/collections';
import Loading from '$lib/components/Common/Loading.svelte';
import Category2InRow from '$lib/components/categories/Category2InRow.svelte';
import Category3InRow from '$lib/components/Categories/preference.svelte';
import { getInterests, getTravelBuddies, getRecommendations, addSelectedPreferences } from '$lib/utils/database/preferences';
import { navigate } from '$lib/router'
import { onMount } from 'svelte';
interface Preference {
name: string;
img: string;
}
//define changing state for displaying different categories
let current_state = 0;
// Define variables to store the preferences
let interests: Array<Preference> | undefined;
let travelBuddies: Array<Preference> | undefined;
let recommendations: Array<Preference> | undefined;
// Define a variable to store the selected preferences
let selectedInterests = [];
let selectedTravelBuddies = [];
let selectedRecommendations = [];
onMount(async () => {
interests = await getInterests();
travelBuddies = await getTravelBuddies();
recommendations = await getRecommendations();
});
// Define a function to handle the form submission
async function submitPreferences() {
await addSelectedPreferences(user, selectedInterests, selectedTravelBuddies, selectedRecommendations);
selectedInterests = [];
}
</script>
{#if preferences.length > 0}
<div class="preferences-page">
<div class="progress-bar"></div>
<h2>What are your interests?</h2>
<Category3InRow
interests={interests.slice(0, 3)}
onPreferenceSelect={updateSelectedInterests}
/>
<h2>Who do you like to travel with?</h2>
<Category2InRow
preferences={preferences.slice(3, 5)}
onPreferenceSelect={updateSelectedInterests[Symbol]...}
/>
<h2>What brought you to our app?</h2>
<Category3InRow
preferences={preferences.slice(5)}
onPreferenceSelect={updateSelected..........}
/>
<button on:click={submitPreferences}>Submit Preferences</button>
</div>
{:else}
<Loading />
{/if}

View File

@ -0,0 +1,167 @@
<script>
import { onMount } from "svelte";
import { client } from "$lib/appwrite";
import { navigate } from "svelte-routing";
import collections from "$lib/collections";
import { getInterests, getRecommendations, getTravelBuddies, addSelectedPreferences } from '$lib/utils/database/preferences';
import preference from "$lib/components/Categories/preference.svelte";
let preferences = [];
let selectedPrefId = 1;
const handleOptionClick = (pref, option) => {
pref.selectedOption = option;
};
const handleNextClick = () => {
selectedPrefId += 1;
};
onMount(async () => {
try {
const [interests, travelBuddies, recommendedBy] = await Promise.all([
getInterests(),
getTravelBuddies(),
getRecommendations(),
]);
preferences = [
{
id: 1,
title: "Select your interests",
options: interests,
selectedOption: null,
},
{
id: 2,
title: "Select your travel buddies",
options: travelBuddies,
selectedOption: null,
},
{
id: 3,
title: "Who recommended you to this app?",
options: recommendedBy,
selectedOption: null,
},
];
const userPreferences = {
userInterests: interests.map((interest) => interest.name),
userTravelBuddy: travelBuddies.map((travelBuddy) => travelBuddy.name),
userRecommended: recommendedBy.map((recommendations) => recommendations.name),
};
try {
await addSelectedPreferences(userPreferences);
navigate("/");
} catch (error) {
console.error(error);
}
} catch (error) {
console.error(error);
console.log(await collections.interests.listDocuments());
}
});
setContext("selectedPrefIndex", selectedPrefIndex);
</script>
{#each preferences as pref}
{#if pref.id === selectedPrefId}
<div class="container">
<div class="container_page">
<h2>{pref.title}</h2>
<div class="radio_container">
{#each pref.options as option}
<label class="item {selected: preferences[selectedPrefIndex].selectedOption === option}">
<input type="radio" name={`pref-${preferences[selectedPrefIndex].id}`} value={option.id} on:change={() => handleOptionClick(preferences[selectedPrefIndex], option)} checked={preferences[selectedPrefIndex].selectedOption === option} />
<div>
<img src={option.img} alt={option.name} />
<p>{option.name}</p>
</div>
</label>
{/each}
</div>
{#if pref.selectedOption}
<button on:click={handleNextClick}>Next</button>
{/if}
</div>
</div>
{/if}
{/each}
{#if selectedPrefId === preferences.length && preferences.every(pref => pref.selectedOption)}
<button on:click={addSelectedPreferences}>Save Preferences</button>
{/if}
<style lang="scss">
.container {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 10px;
overflow-x: hidden;
overflow-y: hidden;
white-space: nowrap;
position: relative;
scroll-snap-type: x mandatory;
height: 100%;
&::-webkit-scrollbar {
display: none;
}
.container_page {
min-width: 100%;
min-height: 100%;
padding: 0px 22px;
scroll-snap-align: center;
display: flex;
flex-direction: column;
gap: 20px;
h2 {
text-align: center;
font-weight: 600;
font-size: 18px;
}
.radio_container {
display: flex;
flex-wrap: wrap;
gap: 10px;
.item {
input {
display: none;
}
div {
display: flex;
flex-direction: column;
align-self: center;
text-align: center;
position: relative;
gap: 8px;
padding: 7px;
width: 145px;
border-radius: 20px;
border: 1px solid #4264eb00;
p {
font-weight: 700;
font-size: 17px;
padding: 0;
}
}
}
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import Interests from "$lib/components/Interests/Interests.svelte"
import Interests from "$lib/components/Categories/interests.svelte"
let current_state = 1
</script>
@ -50,7 +50,9 @@
.selected{
transition-delay: 0.25s;
width: 46px;
background-color: #14A6AE;
outline: 8px solid #BBD1C5;
outline-color: #14A6AE;
border-radius: 16px;
}
}

View File

@ -1,16 +1,37 @@
<script lang="ts">
import Line from '$lib/components/Common/Line.svelte'
import SettingRow from '$lib/components/Common/SettingRow.svelte'
import { Link } from 'svelte-routing'
import Earth from '$lib/svg/Earth.svelte'
import Line from '$lib/components/Common/Line.svelte';
import SettingRow from '$lib/components/Common/SettingRow.svelte';
import { Link } from 'svelte-routing';
import Earth from '$lib/svg/Earth.svelte';
import Sun from '$lib/svg/Sun.svelte';
import PopUp from '$lib/components/Common/PopUp.svelte'
let popUp_Active = false
let popUp_Options = []
let popUp_Curent = ""
function popUp_save(answer:string) {
if (answer === undefined){
popUp_Active = false
}
else{
popUp_Active = false
}
}
function PopUp_open(options:any[]){
popUp_Options = options
popUp_Active=true
}
const items = [
{
title: '',
itms: [{ icon: Sun, text: 'Theme', link:"/"}, { icon: Earth, text: 'Language', link:"/"}],
itms: [{ icon: Sun, text: 'Theme', options:["Dark", "Light"]}, { icon: Earth, text: 'Language', options:["Czech", "English"]}],
}
]
</script>
<Line />
@ -19,13 +40,15 @@
{#if title !== ""}
<div class="mb-4 text-[18px] text-[#61646B]">{title}</div>
{/if}
{#each itms as { icon, text, link}}
{#each itms as { icon, text, options}}
<SettingRow>
<svelte:component this={icon} />
<Link class="font-semibold text-[18px]" to={link}>{text}</Link>
<button class="font-semibold text-[18px]" on:click={() => PopUp_open(options)} >{text}</button>
</SettingRow>
{/each}
{/each}
{#if popUp_Active}
<PopUp headline="Which language would you like to choose?" options={popUp_Options} on_return={popUp_save} default_ans={popUp_Curent}/>
{/if}
</div>

View File

@ -20,8 +20,6 @@
//
$: [userInfo] = collections.users.getDocument([Query.equal('erantId', params.erantId)])
$: console.log($user)
const items = [
{
title: 'Account',
@ -49,34 +47,36 @@
]
</script>
{#if $userInfo}
<div class="w-full h-[188px] flex flex-wrap flex-col gap-4 justify-center items-center mb-8">
<InputPicture />
<span class="font-semibold text-[24px]">{$userInfo.userName}</span>
<SettingRow class="w-auto gap-2">
<PointSmall />
<span class="text-[16px] text-[#61646B]">Prague, Czechia</span>
</SettingRow>
</div>
<Line />
<div class="w-full h-full p-2">
{#if $userInfo}
<div class="w-full h-[188px] flex flex-wrap flex-col gap-4 justify-center items-center mb-8">
<InputPicture />
<span class="font-semibold text-[24px]">{$userInfo.userName}</span>
<SettingRow class="w-auto gap-2">
<PointSmall />
<span class="text-[16px] text-[#61646B]">Prague, Czechia</span>
</SettingRow>
</div>
<Line />
<div class="w-full h-auto flex flex-wrap flex-row mt-4 gap-4">
{#each items as { title, itms }}
<div class="mb-4 text-[18px] text-[#61646B]">{title}</div>
{#each itms as { icon, text, link }}
{#if typeof link === 'function'}
<SettingRow>
<svelte:component this={icon} />
<button class="font-semibold text-[18px]" on:click={link}>{text}</button>
</SettingRow>
{:else}
<SettingRow>
<svelte:component this={icon} />
<Link class="font-semibold text-[18px]" to={link}>{text}</Link>
</SettingRow>
<Line />
{/if}
<div class="w-full h-auto flex flex-wrap flex-row mt-4 gap-4">
{#each items as { title, itms }}
<div class="mb-4 text-[18px] text-[#61646B]">{title}</div>
{#each itms as { icon, text, link }}
{#if typeof link === 'function'}
<SettingRow>
<svelte:component this={icon} />
<button class="font-semibold text-[18px]" on:click={link}>{text}</button>
</SettingRow>
{:else}
<SettingRow>
<svelte:component this={icon} />
<Link class="font-semibold text-[18px]" to={link}>{text}</Link>
</SettingRow>
<Line />
{/if}
{/each}
{/each}
{/each}
</div>
{/if}
</div>
{/if}
</div>

214
src/typography.css Normal file
View File

@ -0,0 +1,214 @@
.display1 {
/* Display Large - Source Sans Pro/Regular 64/72 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 64px;
line-height: 72px; /* or 112% */
letter-spacing: -0.25px;
}
.display2{
/* Display Medium - Source Sans Pro/Regular 48/56 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 48px;
line-height: 56px; /* or 117% */
}
.display3{
/* Display Small - Source Sans Pro/Regular 40/48 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 40px;
line-height: 48px; /* or 120% */
}
.h1{
/* Headline Large - Source Sans Pro/Regular 32/40 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 32px;
line-height: 40px; /* identical to box height, or 125% */
}
.h2{
/* Headline Medium - Source Sans Pro/Regular 28/36 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 28px;
line-height: 36px; /* identical to box height, or 129% */
}
.h3{
/* Headline Small - Source Sans Pro/Regular 24/32 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 32px; /* identical to box height, or 133% */
}
.title1{
/* Title Large - Source Sans Pro/Medium 22/28 . +0.4 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 30px; /* identical to box height, or 125% */
letter-spacing: 0.5px;
}
.title2{
/* Title Medium - Source Sans Pro/Medium 16/24 . +0.16 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 24px; /* identical to box height, or 150% */
letter-spacing: 0.2px;
}
.title3{
/* Title Small - Source Sans Pro/Medium 14/20 . +0.12 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 20px; /* identical to box height, or 143% */
letter-spacing: 0.16px;
}
.label1{
/* Label Large - Source Sans Pro/Medium 16/24 . +0.2 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 18px;
line-height: 28px; /* identical to box height, or 156% */
letter-spacing: 0.2px;
}
.label2{
/* Label Medium - Source Sans Pro/Medium 14/20 . +0.4 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 24px; /* identical to box height, or 150% */
letter-spacing: 0.4px;
}
.label3{
/* Label Small - Source Sans Pro/Medium 12/16 . +0.6 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 20px; /* identical to box height, or 143% */
letter-spacing: 0.6px;
}
.body1{
/* Body Large - Source Sans Pro/Regular 16/24 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px; /* identical to box height, or 150% */
}
.body2{
/* Body Medium - Source Sans Pro/Regular 14/20 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px; /* identical to box height, or 143% */
letter-spacing: 0.08px;
}
.body3{
/* Body Small - Source Sans Pro/Regular 12/16 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px; /* identical to box height, or 133% */
letter-spacing: 0.2px;
}
.overline{
/* Overline - Source Sans Pro/Regular 12/16 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px; /* identical to box height, or 133% */
letter-spacing: 0.8px;
text-transform: uppercase;
}
.caption{
/* Caption - Source Sans Pro/Regular 12/16 . 0 */
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px; /* identical to box height, or 133% */
letter-spacing: 0.5px;
}
.redacted1{
/* Redacted Large */
font-family: 'Redacted Script';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px; /* identical to box height, or 150% */
}
.redacted2{
/* Redacted Medium */
font-family: 'Redacted Script';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px; /* identical to box height, or 143% */
letter-spacing: 0.08px;
}
.redacted3{
/* Redacted Small */
font-family: 'Redacted Script';
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px; /* identical to box height, or 133% */
letter-spacing: 0.2px;
}

View File

@ -1,9 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./index.html', './src/**/*.{html,svelte,js,ts}', './node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}'],
theme: {
extend: {},
},
plugins: [require('flowbite/plugin')],
darkMode: 'class',
content: ['./index.html', './src/**/*.{html,svelte,js,ts}', './node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: [require('flowbite/plugin')],
darkMode: 'class'
}