commit
03608e509c
|
@ -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: {}
|
||||
}
|
||||
|
|
126
index.html
126
index.html
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -2,5 +2,5 @@ const autoprefixer = require('autoprefixer')
|
|||
const tailwind = require('tailwindcss')
|
||||
|
||||
module.exports = {
|
||||
plugins: [tailwind(), autoprefixer()],
|
||||
plugins: [tailwind(), autoprefixer()]
|
||||
}
|
||||
|
|
|
@ -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.
Binary file not shown.
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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) => { })
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})())
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
|
@ -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%;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
import FooterItem from '../Common/NavBar_Item.svelte'
|
||||
|
||||
$: [userInfo] = collections.users.getDocument([Query.equal('userId', $user?.$id || '')])
|
||||
$: console.log($user)
|
||||
|
||||
$: items = [
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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}
|
|
@ -33,3 +33,4 @@
|
|||
{/if}
|
||||
{/each}
|
||||
</Router>
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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'
|
|
@ -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) => {}
|
|
@ -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
|
||||
}
|
|
@ -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];
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
export const getLocationDataFromLatAndLong = async (latitude: number, longitude: number) => {
|
||||
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json`)
|
||||
const data = await response.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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 }
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
'en': () => import('./en.json'),
|
||||
'cz': () => import('./cz.json')
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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'))],
|
||||
)
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue