preferencesPage updated - solved fetching, selection functionality needed

Changes to be committed:
	modified:   .eslintrc.cjs
	modified:   public/serviceworker.js
	modified:   public/serviceworker_notused.js
	modified:   src/__routes.svelte
	modified:   src/lib/collections.ts
	modified:   src/lib/components/Categories/category2InRow.svelte
	modified:   src/lib/components/Categories/category3InRow.svelte
	new file:   src/lib/router/LazyRoute.svelte
	new file:   src/lib/router/ProtectedRouteGuard.svelte
	new file:   src/lib/router/routes.ts
	modified:   src/lib/utils/database/experience.ts
	modified:   src/lib/utils/database/preferences.ts
	modified:   src/lib/utils/parseQuestion.js
	modified:   src/main.js
	modified:   src/routes/onboarding/interestsPage.svelte
	new file:   src/routes/onboarding/preferencesPage.svelte
	modified:   src/routes/profile/functions/Interests-Update.svelte
	modified:   tailwind.config.cjs
This commit is contained in:
matthieu42morin 2023-03-22 07:20:30 +01:00
parent 9aab42db08
commit e56983dc4f
18 changed files with 459 additions and 126 deletions

View File

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

View File

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

View File

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

View File

@ -102,7 +102,7 @@
}, },
{ {
path: '/preferences', path: '/preferences',
component: () => import('$root/src/routes/onboarding/interestsPage.svelte'), component: () => import('$root/src/routes/onboarding/preferencesPage.svelte'),
}, },
]} ]}
/> />

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -2,7 +2,7 @@ import { databases } from '$lib/appwrite'
import { Query } from 'appwrite' import { Query } from 'appwrite'
import database from 'svelte-appwrite-client/src/lib/database' import database from 'svelte-appwrite-client/src/lib/database'
import { getLocationDataFromLatAndLong } from '../locations' import { getLocationDataFromLatAndLong } from '../locations'
import { writable } from 'svelte/store' import { writable } from '$lib/stores/stores'
import { CheckPoint, Experience } from '$lib/TStypes/experiences' import { CheckPoint, Experience } from '$lib/TStypes/experiences'
//Loading of checkpoints and rating is done in the same function to prevent multiple requests to the database //Loading of checkpoints and rating is done in the same function to prevent multiple requests to the database

View File

@ -1,25 +1,76 @@
import { databases } from '$lib/appwrite'; import { databases } from '$lib/appwrite';
import { Query } from 'appwrite'; import { Query } from 'appwrite';
export const getPreferences = async () => { // typescript interfaces for objects return definitions
const { documents } = await databases.listDocuments('6417cf1de159d094b370', '6417cf29f2118829b3b4', [ interface Interest {
Query.orderAsc('Name') name: string;
]); img: string;
}
interface TravelBuddies {
name: string;
img: string;
}
interface Recommendations {
name: string;
img: string;
}
const preferences = documents.map(({ Name, Image }) => ({ name: Name, img: Image })); // Define a variable to store the selected preferences
let selectedInterests = [];
let selectedTravelBuddies = [];
let selectedRecommendations = [];
return preferences; // fetch interests
}; export const getInterests = async (): Promise<Interest[]> => {
return ((await databases.listDocuments('6417cf1de159d094b370', '6417cf29f2118829b3b4')).documents as unknown as Interest[])};
export const addSelectedPreferences = async (userId, preferences) => {
// 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 { document: user } = await databases.getDocument('63ded6c18e8493bffc83', 'Users', userId);
const updatedUser = { const updatedUser = {
...user, ...user,
userPreferences: preferences userInterests: interests,
userTravelBuddy: travelBuddies,
userRecommended: recommendations
}; };
await databases.updateDocument('6417cf1de159d094b370', 'user-collection', user.$id, updatedUser); await databases.updateDocument('63ded6c18e8493bffc83', 'Users', user.$id, updatedUser);
return updatedUser; return updatedUser;
}; };
// // Function to update Interests
// function updateSelectedInterests(preference) {
// if (selectedInterests.includes(preference)) {
// selectedInterests = selectedInterests.filter((item) => item !== preference);
// } else {
// selectedInterests = [...selectedInterests, preference];
// }
// }
// // Function to update Travel Buddies
// function updateSelectedTravelBuddies(preference) {
// if (selectedTravelBuddies.includes(preference)) {
// selectedTravelBuddies = selectedTravelBuddies.filter((item) => item !== preference);
// } else {
// selectedTravelBuddies = [...selectedTravelBuddies, preference];
// }
// }
// // Function to update Recommendations
// function updateSelectedRecommendations(preference) {
// if (selectedRecommendations.includes(preference)) {
// selectedRecommendations = selectedRecommendations.filter((item) => item !== preference);
// } else {
// selectedRecommendations = [...selectedRecommendations, preference];
// }
// }

View File

@ -7,7 +7,7 @@ export default (question, questionType) => {
case 'choice': case 'choice':
return question.split(/;\s*/).map((item) => ({ return question.split(/;\s*/).map((item) => ({
label: item.startsWith('*') ? item.substring(1) : item, label: item.startsWith('*') ? item.substring(1) : item,
value: item.startsWith('*') ? true : false, value: !!item.startsWith('*')
})) }))
default: default:
return null return null

View File

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

View File

@ -1,36 +1,43 @@
<script lang="ts"> <script lang="ts">
import Loading from './../../lib/components/Common/Loading.svelte'; import Interests from './../../lib/components/Categories/interests.svelte';
// Import necessary modules and components // Import necessary modules and components
import { user } from '$lib/appwrite'; import { user } from '$lib/appwrite';
import collections from '$lib/collections'; import collections from '$lib/collections';
import Loading from '$lib/components/Common/Loading.svelte'; import Loading from '$lib/components/Common/Loading.svelte';
import Category2InRow from '$lib/components/categories/Category2InRow.svelte'; import Category2InRow from '$lib/components/categories/Category2InRow.svelte';
import Category3InRow from '$lib/components/categories/Category3InRow.svelte'; import Category3InRow from '$lib/components/categories/Category3InRow.svelte';
import { getPreferences, addSelectedPreferences } from '$lib/utils/database/preferences'; import { getInterests, getTravelBuddies, getRecommendations, addSelectedPreferences } from '$lib/utils/database/preferences';
import { navigate } from '$lib/router' import { navigate } from '$lib/router'
import { onMount } from 'svelte'; 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 preferences
let preferences = [];
// Define a variable to store the selected preferences // Define a variable to store the selected preferences
let selectedPreferences = []; let selectedInterests = [];
let selectedTravelBuddies = [];
let selectedRecommendations = [];
onMount(async () => { onMount(async () => {
preferences = await getPreferences(); interests = await getInterests();
travelBuddies = await getTravelBuddies();
recommendations = await getRecommendations();
}); });
function updateSelectedPreferences(preference) {
if (selectedPreferences.includes(preference)) {
selectedPreferences = selectedPreferences.filter((item) => item !== preference);
} else {
selectedPreferences = [...selectedPreferences, preference];
}
}
// Define a function to handle the form submission // Define a function to handle the form submission
async function submitPreferences() { async function submitPreferences() {
await addSelectedPreferences(user, selectedPreferences); await addSelectedPreferences(user, selectedInterests, selectedTravelBuddies, selectedRecommendations);
selectedPreferences = []; selectedInterests = [];
} }
</script> </script>
@ -39,21 +46,21 @@
<div class="progress-bar"></div> <div class="progress-bar"></div>
<h2>What are your interests?</h2> <h2>What are your interests?</h2>
<Category3InRow <Category3InRow
preferences={preferences.slice(0, 3)} interests={interests.slice(0, 3)}
on:preferenceSelect={updateSelectedPreferences} onPreferenceSelect={updateSelectedInterests}
/> />
<h2>Who do you like to travel with?</h2> <h2>Who do you like to travel with?</h2>
<Category2InRow <Category2InRow
preferences={preferences.slice(3, 5)} preferences={preferences.slice(3, 5)}
on:preferenceSelect={updateSelectedPreferences} onPreferenceSelect={updateSelectedInterests[Symbol]...}
/> />
<h2>What brought you to our app?</h2> <h2>What brought you to our app?</h2>
<Category3InRow <Category3InRow
preferences={preferences.slice(5)} preferences={preferences.slice(5)}
on:preferenceSelect={updateSelectedPreferences} onPreferenceSelect={updateSelected..........}
/> />
<button on:click={submitPreferences}>Submit Preferences</button> <button on:click={submitPreferences}>Submit Preferences</button>
</div> </div>
{:else} {:else}
<Loading> <Loading />
{/if} {/if}

View File

@ -0,0 +1,181 @@
<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'
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>
<main>
{#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}
</main>
<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;
img {
aspect-ratio: 1/1;
object-fit: cover;
border-radius: 18px;
width: 100%;
}
p {
font-weight: 700;
font-size: 17px;
padding: 0;
}
}
&.selected {
div {
border: 1px dashed #4263eb;
p {
color: #4263eb;
}
}
}
}
}
}
}
</style>

View File

@ -50,7 +50,9 @@
.selected{ .selected{
transition-delay: 0.25s; transition-delay: 0.25s;
width: 46px; width: 46px;
background-color: #14A6AE; outline: 8px solid #BBD1C5;
outline-color: #14A6AE;
border-radius: 16px;
} }
} }

View File

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