Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
ab6b909fba
|
|||
|
9fdab2acc9
|
|||
|
68c242ae81
|
|||
|
cb3c1ab05f
|
|||
|
02dae967a2
|
|||
|
77ae081031
|
|||
|
aed60cc0d5
|
|||
|
278c7309b7
|
|||
|
a11b2a0568
|
|||
|
ff8e54449a
|
|||
|
64a59e856f
|
|||
|
5e8c5a1631
|
|||
|
e97949cab3
|
|||
|
b7a3608e67
|
|||
|
bbb544c029
|
|||
|
da42f6ed22
|
|||
|
8016e20451
|
|||
|
64ee8f4fea
|
|||
|
17e8d7dc37
|
|||
|
a409b0a5c7
|
|||
|
6ec4a1e025
|
|||
|
d063b0cf0d
|
|||
|
643d74e29d
|
|||
|
1526a10630
|
|||
|
fc035106d0
|
|||
|
8ae855838b
|
|||
|
9bd10b56d9
|
|||
|
1a78f82c5e
|
|||
|
475ba45248
|
|||
|
2a949d771a
|
@@ -1,2 +0,0 @@
|
||||
APPWRITE_API_ENDPOINT='https://appwrite.oys.undock.ca/v1'
|
||||
APPWRITE_API_PROJECT='bab'
|
||||
@@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} is building a BAB App artifact 🚀
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- devel
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -34,4 +34,7 @@ yarn-error.log*
|
||||
*.sln
|
||||
|
||||
# local .env files
|
||||
.env.local*
|
||||
.env*
|
||||
|
||||
# version file
|
||||
src/version.js
|
||||
23
.releaserc.json
Normal file
23
.releaserc.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"branches": [
|
||||
"main",
|
||||
"next",
|
||||
{ "name": "beta", "prerelease": true },
|
||||
{ "name": "alpha", "prerelease": true }
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@saithodev/semantic-release-gitea",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "dist/build-*.tar.gz",
|
||||
"label": "package distribution"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -41,3 +41,7 @@ quasar build
|
||||
### Customize the configuration
|
||||
|
||||
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
||||
|
||||
### TODO
|
||||
|
||||
https://github.com/semantic-release/semantic-release
|
||||
|
||||
28
generate-version.js
Normal file
28
generate-version.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
try {
|
||||
// Run semantic-release to get the next version number
|
||||
const dryRunOutput = execSync('npx semantic-release --dry-run').toString();
|
||||
|
||||
// Extract the version number from the semantic-release output
|
||||
const versionMatch = dryRunOutput.match(
|
||||
/The next release version is ([\S]+)/
|
||||
);
|
||||
if (!versionMatch) {
|
||||
throw new Error('Version number not found in semantic-release output');
|
||||
}
|
||||
const version = versionMatch[1];
|
||||
|
||||
// Create version content
|
||||
const versionContent = `export const APP_VERSION = '${version}';\n`;
|
||||
const versionFilePath = path.resolve(__dirname, 'src/version.js');
|
||||
|
||||
// Write version to file
|
||||
fs.writeFileSync(versionFilePath, versionContent, 'utf8');
|
||||
console.log(`Version file generated with version: ${version}`);
|
||||
} catch (error) {
|
||||
console.error('Error generating version file:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
36
nohup.out
Normal file
36
nohup.out
Normal file
@@ -0,0 +1,36 @@
|
||||
2024-06-06 07:42:15,841 - vorta.i18n - DEBUG - Loading translation failed for ['en-CA', 'en-Latn-CA'].
|
||||
QObject::connect: No such signal QPlatformNativeInterface::systemTrayWindowChanged(QScreen*)
|
||||
2024-06-06 07:42:15,884 - root - DEBUG - Not a private SSH key file: authorized_keys
|
||||
2024-06-06 07:42:15,885 - root - DEBUG - Not a private SSH key file: github_rsa.pub_bak-github
|
||||
2024-06-06 07:42:15,886 - root - DEBUG - Not a private SSH key file: other_keys.seahorse
|
||||
2024-06-06 07:42:16,077 - root - INFO - Using NetworkManagerMonitor NetworkStatusMonitor implementation.
|
||||
Requested decoration "adwaita" not found, falling back to default
|
||||
qt.qpa.wayland: Wayland does not support QWindow::requestActivate()
|
||||
2024-06-06 07:42:16,209 - vorta.borg.jobs_manager - DEBUG - Add job for site default
|
||||
2024-06-06 07:42:16,210 - vorta.borg.jobs_manager - DEBUG - Start job on site: default
|
||||
2024-06-06 07:42:16,237 - vorta.borg.borg_job - INFO - Running command /usr/bin/borg --version
|
||||
2024-06-06 07:42:20,564 - vorta.borg.jobs_manager - DEBUG - Finish job for site: default
|
||||
2024-06-06 07:42:20,565 - vorta.borg.jobs_manager - DEBUG - No more jobs for site: default
|
||||
2024-06-06 07:42:20,566 - vorta.scheduler - DEBUG - Refreshing all scheduler timers
|
||||
2024-06-06 07:42:20,568 - vorta.scheduler - DEBUG - Nothing scheduled for profile 1 because of unset repo.
|
||||
qt.qpa.wayland: Wayland does not support QWindow::requestActivate()
|
||||
2024-06-06 07:42:23,190 - root - DEBUG - Not a private SSH key file: authorized_keys
|
||||
2024-06-06 07:42:23,191 - root - DEBUG - Not a private SSH key file: github_rsa.pub_bak-github
|
||||
2024-06-06 07:42:23,191 - root - DEBUG - Not a private SSH key file: other_keys.seahorse
|
||||
2024-06-06 07:42:23,204 - vorta.keyring.abc - DEBUG - Only available on macOS
|
||||
2024-06-06 07:42:23,244 - asyncio - DEBUG - Using selector: EpollSelector
|
||||
2024-06-06 07:42:23,245 - vorta.keyring.abc - DEBUG - Using VortaSecretStorageKeyring
|
||||
2024-06-06 07:49:53,786 - vorta.keyring.abc - DEBUG - Only available on macOS
|
||||
2024-06-06 07:49:53,788 - asyncio - DEBUG - Using selector: EpollSelector
|
||||
2024-06-06 07:49:53,788 - vorta.keyring.abc - DEBUG - Using VortaSecretStorageKeyring
|
||||
2024-06-06 07:49:53,789 - asyncio - DEBUG - Using selector: EpollSelector
|
||||
2024-06-06 07:49:53,790 - vorta.keyring.secretstorage - DEBUG - Found 0 passwords matching repo URL.
|
||||
qt.qpa.wayland: Wayland does not support QWindow::requestActivate()
|
||||
2024-06-06 07:50:10,009 - vorta.keyring.abc - DEBUG - Only available on macOS
|
||||
2024-06-06 07:50:10,011 - asyncio - DEBUG - Using selector: EpollSelector
|
||||
2024-06-06 07:50:10,012 - vorta.keyring.abc - DEBUG - Using VortaSecretStorageKeyring
|
||||
2024-06-06 07:50:10,012 - vorta.borg.borg_job - DEBUG - Using VortaSecretStorageKeyring keyring to store passwords.
|
||||
2024-06-06 07:50:10,013 - asyncio - DEBUG - Using selector: EpollSelector
|
||||
2024-06-06 07:50:10,013 - vorta.keyring.secretstorage - DEBUG - Found 0 passwords matching repo URL.
|
||||
2024-06-06 07:50:10,013 - vorta.borg.borg_job - DEBUG - Password not found in primary keyring. Falling back to VortaDBKeyring.
|
||||
2024-06-06 07:50:10,029 - vorta.borg.borg_job - INFO - Running command /usr/bin/borg info --info --json --log-json ssh://borg@borg.toal.ca:12022/./ptoal-linux
|
||||
13
package.json
13
package.json
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "oys_bab",
|
||||
"version": "0.6.1",
|
||||
"version": "0.0.0",
|
||||
"description": "Manage a Borrow a Boat program for a Yacht Club",
|
||||
"productName": "OYS Borrow a Boat",
|
||||
"author": "Patrick Toal <ptoal@takeflight.ca>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"generate-version": "node generate-version.js",
|
||||
"lint": "eslint --ext .js,.ts,.vue ./",
|
||||
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||
"test": "echo \"No test specified\" && exit 0",
|
||||
"dev": "quasar dev",
|
||||
"build": "quasar build"
|
||||
"dev": "npm run generate-version && quasar dev",
|
||||
"build": "npm run generate-version && quasar build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.11",
|
||||
@@ -26,6 +27,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/app-vite": "^1.9.1",
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/github": "^10.0.6",
|
||||
"@semantic-release/npm": "^12.0.1",
|
||||
"@types/node": "^12.20.21",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
@@ -34,8 +39,10 @@
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-plugin-vue": "^9.0.0",
|
||||
"git-commit-info": "^2.0.2",
|
||||
"prettier": "^2.5.1",
|
||||
"quasar": "^2.16.0",
|
||||
"semantic-release": "^24.0.0",
|
||||
"typescript": "~5.3.0",
|
||||
"vite-plugin-checker": "^0.6.4",
|
||||
"vue-tsc": "^1.8.22",
|
||||
|
||||
BIN
public/tmpimg/projectX.jpg
Normal file
BIN
public/tmpimg/projectX.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
@@ -10,7 +10,7 @@
|
||||
|
||||
const { configure } = require('quasar/wrappers');
|
||||
|
||||
module.exports = configure(function (/* ctx */) {
|
||||
module.exports = configure(function () {
|
||||
return {
|
||||
eslint: {
|
||||
// fix: true,
|
||||
@@ -48,7 +48,6 @@ module.exports = configure(function (/* ctx */) {
|
||||
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
|
||||
build: {
|
||||
env: require('dotenv').config().parsed,
|
||||
target: {
|
||||
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||
node: 'node16',
|
||||
@@ -102,12 +101,19 @@ module.exports = configure(function (/* ctx */) {
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
},
|
||||
'/function': {
|
||||
target: 'https://6640382951eacb568371.f.appwrite.toal.ca/',
|
||||
'/api/v1/realtime': {
|
||||
target: 'wss://apidev.bab.toal.ca',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/function/, ''),
|
||||
ws: true,
|
||||
},
|
||||
// '/function': {
|
||||
// target: 'https://6640382951eacb568371.f.appwrite.toal.ca/',
|
||||
// changeOrigin: true,
|
||||
// secure: false,
|
||||
// rewrite: (path) => path.replace(/^\/function/, ''),
|
||||
// },
|
||||
},
|
||||
// For reverse-proxying via haproxy
|
||||
// hmr: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"orientation": "portrait",
|
||||
"orientation": "natural",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#027be3",
|
||||
"icons": [
|
||||
|
||||
@@ -9,33 +9,35 @@ register(process.env.SERVICE_WORKER_FILE, {
|
||||
// to ServiceWorkerContainer.register()
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#Parameter
|
||||
|
||||
// registrationOptions: { scope: './' },
|
||||
registrationOptions: { scope: './' },
|
||||
|
||||
ready (/* registration */) {
|
||||
// console.log('Service worker is active.')
|
||||
ready(/* registration */) {
|
||||
console.log('Service worker is active.');
|
||||
},
|
||||
|
||||
registered (/* registration */) {
|
||||
// console.log('Service worker has been registered.')
|
||||
registered(/* registration */) {
|
||||
console.log('Service worker has been registered.');
|
||||
},
|
||||
|
||||
cached (/* registration */) {
|
||||
// console.log('Content has been cached for offline use.')
|
||||
cached(/* registration */) {
|
||||
console.log('Content has been cached for offline use.');
|
||||
},
|
||||
|
||||
updatefound (/* registration */) {
|
||||
// console.log('New content is downloading.')
|
||||
updatefound(/* registration */) {
|
||||
console.log('New content is downloading.');
|
||||
},
|
||||
|
||||
updated (/* registration */) {
|
||||
// console.log('New content is available; please refresh.')
|
||||
updated(/* registration */) {
|
||||
console.log('New content is available; please refresh.');
|
||||
},
|
||||
|
||||
offline () {
|
||||
// console.log('No internet connection found. App is running in offline mode.')
|
||||
offline() {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
},
|
||||
|
||||
error (/* err */) {
|
||||
// console.error('Error during service worker registration:', err)
|
||||
error(err) {
|
||||
console.error('Error during service worker registration:', err);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
<script setup lang="ts">
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { useAuthStore } from './stores/auth';
|
||||
import { useBoatStore } from './stores/boat';
|
||||
import { useReservationStore } from './stores/reservation';
|
||||
|
||||
defineComponent({
|
||||
name: 'OYS Borrow-a-Boat',
|
||||
@@ -14,7 +12,5 @@ defineComponent({
|
||||
|
||||
onMounted(async () => {
|
||||
await useAuthStore().init();
|
||||
await useBoatStore().fetchBoats();
|
||||
await useReservationStore().fetchUserReservations();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -14,56 +14,72 @@ import type { Router } from 'vue-router';
|
||||
|
||||
const client = new Client();
|
||||
|
||||
let APPWRITE_API_ENDPOINT, APPWRITE_API_PROJECT;
|
||||
const API_ENDPOINT = import.meta.env.VITE_APPWRITE_API_ENDPOINT;
|
||||
const API_PROJECT = import.meta.env.VITE_APPWRITE_API_PROJECT;
|
||||
|
||||
// Private self-hosted appwrite
|
||||
if (process.env.APPWRITE_API_ENDPOINT && process.env.APPWRITE_API_PROJECT) {
|
||||
APPWRITE_API_ENDPOINT = process.env.APPWRITE_API_ENDPOINT;
|
||||
APPWRITE_API_PROJECT = process.env.APPWRITE_API_PROJECT;
|
||||
} else if (process.env.DEV) {
|
||||
APPWRITE_API_ENDPOINT = 'http://localhost:4000/api/v1';
|
||||
APPWRITE_API_PROJECT = '65ede55a213134f2b688';
|
||||
if (API_ENDPOINT && API_PROJECT) {
|
||||
client.setEndpoint(API_ENDPOINT).setProject(API_PROJECT);
|
||||
} else {
|
||||
APPWRITE_API_ENDPOINT = 'https://appwrite.oys.undock.ca/v1';
|
||||
APPWRITE_API_PROJECT = 'bab';
|
||||
console.error(
|
||||
'Must configure VITE_APPWRITE_API_ENDPOINT and VITE_APPWRITE_API_PROJECT'
|
||||
);
|
||||
}
|
||||
client.setEndpoint(APPWRITE_API_ENDPOINT).setProject(APPWRITE_API_PROJECT);
|
||||
|
||||
const pwresetUrl = process.env.DEV
|
||||
? 'http://localhost:4000/pwreset'
|
||||
: 'https://oys.undock.ca/pwreset';
|
||||
type AppwriteIDConfig = {
|
||||
databaseId: string;
|
||||
collection: {
|
||||
boat: string;
|
||||
reservation: string;
|
||||
skillTags: string;
|
||||
task: string;
|
||||
taskTags: string;
|
||||
interval: string;
|
||||
intervalTemplate: string;
|
||||
};
|
||||
function: {
|
||||
userinfo: string;
|
||||
};
|
||||
};
|
||||
|
||||
const AppwriteIds = process.env.DEV
|
||||
? {
|
||||
databaseId: '65ee1cbf9c2493faf15f',
|
||||
collection: {
|
||||
boat: 'boat',
|
||||
reservation: 'reservation',
|
||||
skillTags: 'skillTags',
|
||||
task: 'task',
|
||||
taskTags: 'taskTags',
|
||||
interval: 'interval',
|
||||
intervalTemplate: 'intervalTemplate',
|
||||
},
|
||||
function: {
|
||||
userinfo: 'userinfo',
|
||||
},
|
||||
}
|
||||
: {
|
||||
databaseId: 'bab_prod',
|
||||
collection: {
|
||||
boat: 'boat',
|
||||
reservation: 'reservation',
|
||||
skillTags: 'skillTags',
|
||||
task: 'task',
|
||||
taskTags: 'taskTags',
|
||||
interval: 'interval',
|
||||
intervalTemplate: 'intervalTemplate',
|
||||
},
|
||||
function: {
|
||||
userinfo: '664038294b5473ef0c8d',
|
||||
},
|
||||
};
|
||||
let AppwriteIds = <AppwriteIDConfig>{};
|
||||
|
||||
console.log(API_ENDPOINT);
|
||||
if (
|
||||
API_ENDPOINT === 'https://apidev.bab.toal.ca/v1' ||
|
||||
API_ENDPOINT === 'http://localhost:4000/api/v1'
|
||||
) {
|
||||
AppwriteIds = {
|
||||
databaseId: '65ee1cbf9c2493faf15f',
|
||||
collection: {
|
||||
boat: 'boat',
|
||||
reservation: 'reservation',
|
||||
skillTags: 'skillTags',
|
||||
task: 'task',
|
||||
taskTags: 'taskTags',
|
||||
interval: 'interval',
|
||||
intervalTemplate: 'intervalTemplate',
|
||||
},
|
||||
function: {
|
||||
userinfo: 'userinfo',
|
||||
},
|
||||
};
|
||||
} else if (API_ENDPOINT === 'https://appwrite.oys.undock.ca/v1') {
|
||||
AppwriteIds = {
|
||||
databaseId: 'bab_prod',
|
||||
collection: {
|
||||
boat: 'boat',
|
||||
reservation: 'reservation',
|
||||
skillTags: 'skillTags',
|
||||
task: 'task',
|
||||
taskTags: 'taskTags',
|
||||
interval: 'interval',
|
||||
intervalTemplate: 'intervalTemplate',
|
||||
},
|
||||
function: {
|
||||
userinfo: '664038294b5473ef0c8d',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const account = new Account(client);
|
||||
const databases = new Databases(client);
|
||||
@@ -120,6 +136,7 @@ async function login(email: string, password: string) {
|
||||
});
|
||||
appRouter.replace({ name: 'index' });
|
||||
} catch (error: unknown) {
|
||||
console.log(error);
|
||||
if (error instanceof AppwriteException) {
|
||||
if (error.type === 'user_session_already_exists') {
|
||||
appRouter.replace({ name: 'index' });
|
||||
@@ -147,7 +164,7 @@ async function login(email: string, password: string) {
|
||||
}
|
||||
|
||||
async function resetPassword(email: string) {
|
||||
await account.createRecovery(email, pwresetUrl);
|
||||
await account.createRecovery(email, window.location.origin + '/pwreset');
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -164,7 +164,7 @@ const reservationStore = useReservationStore();
|
||||
const boatSelect = ref(false);
|
||||
const bookingForm = ref<BookingForm>({ ...newForm });
|
||||
const $q = useQuasar();
|
||||
const router = useRouter();
|
||||
const $router = useRouter();
|
||||
|
||||
watch(reservation, (newReservation) => {
|
||||
if (!newReservation) {
|
||||
@@ -183,7 +183,7 @@ watch(reservation, (newReservation) => {
|
||||
}
|
||||
});
|
||||
|
||||
const updateInterval = (interval: Interval) => {
|
||||
const updateInterval = (interval: Interval | null) => {
|
||||
bookingForm.value.interval = interval;
|
||||
boatSelect.value = false;
|
||||
};
|
||||
@@ -210,7 +210,8 @@ const boat = computed((): Boat | null => {
|
||||
});
|
||||
|
||||
const onDelete = () => {
|
||||
reservationStore.deleteReservation(reservation.value?.id);
|
||||
reservationStore.deleteReservation(reservation.value?.$id);
|
||||
$router.go(-1);
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
@@ -279,6 +280,6 @@ const onSubmit = async () => {
|
||||
message: 'Failed to book!' + e,
|
||||
});
|
||||
}
|
||||
router.go(-1);
|
||||
$router.go(-1);
|
||||
};
|
||||
</script>
|
||||
|
||||
19
src/components/DiscordOauthComponent.vue
Normal file
19
src/components/DiscordOauthComponent.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<q-btn
|
||||
@click="auth.discordLogin()"
|
||||
style="width: 300px">
|
||||
<q-avatar
|
||||
left
|
||||
size="sm"
|
||||
class="q-ma-xs">
|
||||
<img
|
||||
src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/636e0a6a49cf127bf92de1e2_icon_clyde_blurple_RGB.png" />
|
||||
</q-avatar>
|
||||
Login with Discord
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from 'src/stores/auth';
|
||||
const auth = useAuthStore();
|
||||
</script>
|
||||
@@ -1,5 +1,16 @@
|
||||
<template>
|
||||
<div @click="auth.googleLogin()">Login with Google</div>
|
||||
<q-btn
|
||||
@click="auth.googleLogin()"
|
||||
style="width: 300px">
|
||||
<q-avatar
|
||||
left
|
||||
class="q-ma-xs"
|
||||
size="sm">
|
||||
<img
|
||||
src="https://lh3.googleusercontent.com/COxitqgJr1sJnIDe8-jiKhxDx1FrYbtRHKJ9z_hELisAlapwE9LUPh6fcXIfb5vwpbMl4xl9H9TRFPc5NOO8Sb3VSgIBrfRYvW6cUA" />
|
||||
</q-avatar>
|
||||
<div>Login with Google</div>
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
62
src/components/NewPasswordComponent.vue
Normal file
62
src/components/NewPasswordComponent.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<q-card-section class="q-ma-sm">
|
||||
<q-input
|
||||
v-model="password"
|
||||
label="New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<q-input
|
||||
v-model="confirmPassword"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<div class="text-caption q-py-md">Enter a new password.</div>
|
||||
</q-card-section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
|
||||
const newPassword = defineModel();
|
||||
|
||||
const validatePasswordStrength = (val: string) => {
|
||||
const hasUpperCase = /[A-Z]/.test(val);
|
||||
const hasLowerCase = /[a-z]/.test(val);
|
||||
const hasNumbers = /[0-9]/.test(val);
|
||||
const hasNonAlphas = /[\W_]/.test(val);
|
||||
const isValidLength = val.length >= 8;
|
||||
|
||||
return (
|
||||
(hasUpperCase &&
|
||||
hasLowerCase &&
|
||||
hasNumbers &&
|
||||
hasNonAlphas &&
|
||||
isValidLength) ||
|
||||
'Password must be at least 8 characters long and include uppercase, lowercase, number, and special character.'
|
||||
);
|
||||
};
|
||||
|
||||
const validatePasswordsMatch = (val: string) => {
|
||||
return val === password.value || 'Passwords do not match.';
|
||||
};
|
||||
|
||||
watch([password, confirmPassword], ([newpw, newpw1]) => {
|
||||
if (
|
||||
validatePasswordStrength(newpw) === true &&
|
||||
validatePasswordsMatch(newpw1) === true
|
||||
) {
|
||||
newPassword.value = newpw;
|
||||
} else {
|
||||
newPassword.value = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -184,7 +184,7 @@ function getEvents(scope: ResourceIntervalScope) {
|
||||
scope.resource.$id
|
||||
);
|
||||
|
||||
return resourceEvents.map((event) => {
|
||||
return resourceEvents.value.map((event) => {
|
||||
return {
|
||||
left: scope.timeStartPosX(parsed(event.start)),
|
||||
width: scope.timeDurationWidth(
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<q-toolbar-title>{{ pageTitle }}</q-toolbar-title>
|
||||
<q-space />
|
||||
<div>v2024.6.4.2</div>
|
||||
<div>v{{ APP_VERSION }}</div>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<LeftDrawer
|
||||
@@ -22,6 +22,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import LeftDrawer from 'components/LeftDrawer.vue';
|
||||
import { APP_VERSION } from 'src/version';
|
||||
|
||||
const leftDrawerOpen = ref(false);
|
||||
function toggleLeftDrawer() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<q-card
|
||||
v-for="boat in boats"
|
||||
:key="boat.id"
|
||||
class="mobile-card q-ma-sm">
|
||||
class="q-ma-sm">
|
||||
<q-card-section>
|
||||
<q-img
|
||||
:src="boat.imgSrc"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
v-for="block in getAvailableIntervals(
|
||||
scope.timestamp,
|
||||
boats[scope.columnIndex]
|
||||
)"
|
||||
).value"
|
||||
:key="block.$id">
|
||||
<div
|
||||
class="timeblock"
|
||||
@@ -207,7 +207,7 @@ function selectBlock(event: MouseEvent, scope: DayBodyScope, block: Interval) {
|
||||
const boatReservations = computed((): Record<string, Reservation[]> => {
|
||||
return reservationStore
|
||||
.getReservationsByDate(selectedDate.value)
|
||||
.reduce((result, reservation) => {
|
||||
.value.reduce((result, reservation) => {
|
||||
if (!result[reservation.resource]) result[reservation.resource] = [];
|
||||
result[reservation.resource].push(reservation);
|
||||
return result;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="col text-h6">Log in</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-form>
|
||||
<q-form @keydown.enter.prevent="doTokenLogin">
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input
|
||||
v-model="email"
|
||||
@@ -23,35 +23,37 @@
|
||||
color="darkblue"
|
||||
filled></q-input>
|
||||
<q-input
|
||||
v-model="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
v-if="userId"
|
||||
v-model="token"
|
||||
label="6-digit code"
|
||||
type="number"
|
||||
color="darkblue"
|
||||
filled></q-input>
|
||||
<q-card-actions>
|
||||
<q-btn
|
||||
type="button"
|
||||
@click="doLogin"
|
||||
label="Login"
|
||||
color="primary"></q-btn>
|
||||
<q-space />
|
||||
<q-btn
|
||||
flat
|
||||
color="secondary"
|
||||
to="/pwreset">
|
||||
Reset password
|
||||
</q-btn>
|
||||
<!-- <q-btn
|
||||
type="button"
|
||||
@click="register"
|
||||
color="secondary"
|
||||
label="Register"
|
||||
flat
|
||||
></q-btn> -->
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
<!-- <q-card-section><GoogleOauthComponent /></q-card-section> -->
|
||||
<q-card-section class="q-pa-none">
|
||||
<div class="row justify-center q-ma-sm">
|
||||
<q-btn
|
||||
type="button"
|
||||
@click="doTokenLogin"
|
||||
color="primary"
|
||||
label="Login with E-mail"
|
||||
style="width: 300px" />
|
||||
</div>
|
||||
<div class="row justify-center q-ma-sm">
|
||||
<GoogleOauthComponent />
|
||||
</div>
|
||||
<div class="row justify-center q-ma-sm">
|
||||
<DiscordOauthComponent />
|
||||
</div>
|
||||
<div class="row justify-center">
|
||||
<q-btn
|
||||
flat
|
||||
color="secondary"
|
||||
to="/pwreset"
|
||||
label="Forgot Password?" />
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
@@ -75,13 +77,77 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { login } from 'boot/appwrite';
|
||||
// import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
||||
import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
||||
import DiscordOauthComponent from 'src/components/DiscordOauthComponent.vue';
|
||||
import { Dialog, Notify } from 'quasar';
|
||||
import { useAuthStore } from 'src/stores/auth';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { AppwriteException } from 'appwrite';
|
||||
import { APP_VERSION } from 'src/version.js';
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const token = ref('');
|
||||
const userId = ref();
|
||||
const router = useRouter();
|
||||
|
||||
const doLogin = async () => {
|
||||
login(email.value, password.value);
|
||||
console.log('version:' + APP_VERSION);
|
||||
|
||||
const doTokenLogin = async () => {
|
||||
const authStore = useAuthStore();
|
||||
if (!userId.value) {
|
||||
try {
|
||||
const sessionToken = await authStore.createTokenSession(email.value);
|
||||
userId.value = sessionToken.userId;
|
||||
Dialog.create({ message: 'Check your e-mail for your login code.' });
|
||||
} catch (e) {
|
||||
Dialog.create({
|
||||
message: 'An error occurred. Please ask for help in Discord',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const notification = Notify.create({
|
||||
type: 'primary',
|
||||
position: 'top',
|
||||
spinner: true,
|
||||
message: 'Logging you in...',
|
||||
timeout: 8000,
|
||||
group: false,
|
||||
});
|
||||
try {
|
||||
await authStore.tokenLogin(userId.value, token.value);
|
||||
notification({
|
||||
type: 'positive',
|
||||
message: 'Logged in!',
|
||||
timeout: 2000,
|
||||
spinner: false,
|
||||
icon: 'check_circle',
|
||||
});
|
||||
router.replace({ name: 'index' });
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AppwriteException) {
|
||||
if (error.type === 'user_session_already_exists') {
|
||||
useRouter().replace({ name: 'index' });
|
||||
notification({
|
||||
type: 'positive',
|
||||
message: 'Already Logged in!',
|
||||
timeout: 2000,
|
||||
spinner: false,
|
||||
icon: 'check_circle',
|
||||
});
|
||||
return;
|
||||
}
|
||||
Dialog.create({
|
||||
title: 'Login Error!',
|
||||
message: error.message,
|
||||
persistent: true,
|
||||
});
|
||||
}
|
||||
notification({
|
||||
type: 'negative',
|
||||
message: 'Login failed.',
|
||||
timeout: 2000,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,163 +1,173 @@
|
||||
<template>
|
||||
<q-page padding>
|
||||
<h1>Privacy Policy for bab.toal.ca</h1>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page padding>
|
||||
<h1>Privacy Policy for Undock.ca</h1>
|
||||
|
||||
<p>
|
||||
At OYS BAB Test, accessible from https://bab.toal.ca, one of our main
|
||||
priorities is the privacy of our visitors. This Privacy Policy document
|
||||
contains types of information that is collected and recorded by OYS BAB
|
||||
Test and how we use it.
|
||||
</p>
|
||||
<p>
|
||||
At Undock, accessible from https://undock.ca, one of our main
|
||||
priorities is the privacy of our visitors. This Privacy Policy
|
||||
document contains types of information that is collected and recorded
|
||||
by Undock and how we use it.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you have additional questions or require more information about our
|
||||
Privacy Policy, do not hesitate to contact us. Our Privacy Policy was
|
||||
generated with the help of
|
||||
<a href="https://www.gdprprivacypolicy.net/"
|
||||
>GDPR Privacy Policy Generator</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
If you have additional questions or require more information about our
|
||||
Privacy Policy, do not hesitate to contact us. Our Privacy Policy was
|
||||
generated with the help of
|
||||
<a href="https://www.gdprprivacypolicy.net/">
|
||||
GDPR Privacy Policy Generator
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2>General Data Protection Regulation (GDPR)</h2>
|
||||
<p>We are a Data Controller of your information.</p>
|
||||
<h2>General Data Protection Regulation (GDPR)</h2>
|
||||
<p>We are a Data Controller of your information.</p>
|
||||
|
||||
<p>
|
||||
bab.toal.ca legal basis for collecting and using the personal information
|
||||
described in this Privacy Policy depends on the Personal Information we
|
||||
collect and the specific context in which we collect the information:
|
||||
</p>
|
||||
<ul>
|
||||
<li>bab.toal.ca needs to perform a contract with you</li>
|
||||
<li>You have given bab.toal.ca permission to do so</li>
|
||||
<li>
|
||||
Processing your personal information is in bab.toal.ca legitimate
|
||||
interests
|
||||
</li>
|
||||
<li>bab.toal.ca needs to comply with the law</li>
|
||||
</ul>
|
||||
<p>
|
||||
Undock's legal basis for collecting and using the personal information
|
||||
described in this Privacy Policy depends on the Personal Information
|
||||
we collect and the specific context in which we collect the
|
||||
information:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Undock needs to perform a contract with you</li>
|
||||
<li>You have given Undock permission to do so</li>
|
||||
<li>
|
||||
Processing your personal information is in Undock legitimate
|
||||
interests
|
||||
</li>
|
||||
<li>Undock needs to comply with the law</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
bab.toal.ca will retain your personal information only for as long as is
|
||||
necessary for the purposes set out in this Privacy Policy. We will retain
|
||||
and use your information to the extent necessary to comply with our legal
|
||||
obligations, resolve disputes, and enforce our policies.
|
||||
</p>
|
||||
<p>
|
||||
Undock will retain your personal information only for as long as is
|
||||
necessary for the purposes set out in this Privacy Policy. We will
|
||||
retain and use your information to the extent necessary to comply with
|
||||
our legal obligations, resolve disputes, and enforce our policies.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are a resident of the European Economic Area (EEA), you have
|
||||
certain data protection rights. If you wish to be informed what Personal
|
||||
Information we hold about you and if you want it to be removed from our
|
||||
systems, please contact us.
|
||||
</p>
|
||||
<p>
|
||||
If you are a resident of the European Economic Area (EEA), you have
|
||||
certain data protection rights. If you wish to be informed what
|
||||
Personal Information we hold about you and if you want it to be
|
||||
removed from our systems, please contact us.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In certain circumstances, you have the following data protection rights:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
The right to access, update or to delete the information we have on you.
|
||||
</li>
|
||||
<li>The right of rectification.</li>
|
||||
<li>The right to object.</li>
|
||||
<li>The right of restriction.</li>
|
||||
<li>The right to data portability</li>
|
||||
<li>The right to withdraw consent</li>
|
||||
</ul>
|
||||
<p>
|
||||
In certain circumstances, you have the following data protection
|
||||
rights:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
The right to access, update or to delete the information we have on
|
||||
you.
|
||||
</li>
|
||||
<li>The right of rectification.</li>
|
||||
<li>The right to object.</li>
|
||||
<li>The right of restriction.</li>
|
||||
<li>The right to data portability</li>
|
||||
<li>The right to withdraw consent</li>
|
||||
</ul>
|
||||
|
||||
<h2>Log Files</h2>
|
||||
<h2>Log Files</h2>
|
||||
|
||||
<p>
|
||||
OYS BAB Test follows a standard procedure of using log files. These files
|
||||
log visitors when they visit websites. All hosting companies do this and a
|
||||
part of hosting services' analytics. The information collected by log
|
||||
files include internet protocol (IP) addresses, browser type, Internet
|
||||
Service Provider (ISP), date and time stamp, referring/exit pages, and
|
||||
possibly the number of clicks. These are not linked to any information
|
||||
that is personally identifiable. The purpose of the information is for
|
||||
analyzing trends, administering the site, tracking users' movement on the
|
||||
website, and gathering demographic information.
|
||||
</p>
|
||||
<p>
|
||||
Undock follows a standard procedure of using log files. These files
|
||||
log visitors when they visit websites. All hosting companies do this
|
||||
and a part of hosting services' analytics. The information collected
|
||||
by log files include internet protocol (IP) addresses, browser type,
|
||||
Internet Service Provider (ISP), date and time stamp, referring/exit
|
||||
pages, and possibly the number of clicks. These are not linked to any
|
||||
information that is personally identifiable. The purpose of the
|
||||
information is for analyzing trends, administering the site, tracking
|
||||
users' movement on the website, and gathering demographic information.
|
||||
</p>
|
||||
|
||||
<h2>Cookies and Web Beacons</h2>
|
||||
<h2>Cookies and Web Beacons</h2>
|
||||
|
||||
<p>
|
||||
Like any other website, OYS BAB Test uses "cookies". These cookies are
|
||||
used to store information including visitors' preferences, and the pages
|
||||
on the website that the visitor accessed or visited. The information is
|
||||
used to optimize the users' experience by customizing our web page content
|
||||
based on visitors' browser type and/or other information.
|
||||
</p>
|
||||
<p>
|
||||
Like any other website, Undock uses "cookies". These cookies are used
|
||||
to store information including visitors' preferences, and the pages on
|
||||
the website that the visitor accessed or visited. The information is
|
||||
used to optimize the users' experience by customizing our web page
|
||||
content based on visitors' browser type and/or other information.
|
||||
</p>
|
||||
|
||||
<h2>Privacy Policies</h2>
|
||||
<h2>Privacy Policies</h2>
|
||||
|
||||
<P
|
||||
>You may consult this list to find the Privacy Policy for each of the
|
||||
advertising partners of OYS BAB Test.</P
|
||||
>
|
||||
<P>
|
||||
You may consult this list to find the Privacy Policy for each of the
|
||||
advertising partners of Undock.
|
||||
</P>
|
||||
|
||||
<p>
|
||||
Third-party ad servers or ad networks uses technologies like cookies,
|
||||
JavaScript, or Web Beacons that are used in their respective
|
||||
advertisements and links that appear on OYS BAB Test, which are sent
|
||||
directly to users' browser. They automatically receive your IP address
|
||||
when this occurs. These technologies are used to measure the effectiveness
|
||||
of their advertising campaigns and/or to personalize the advertising
|
||||
content that you see on websites that you visit.
|
||||
</p>
|
||||
<p>
|
||||
Third-party ad servers or ad networks uses technologies like cookies,
|
||||
JavaScript, or Web Beacons that are used in their respective
|
||||
advertisements and links that appear on Undock, which are sent
|
||||
directly to users' browser. They automatically receive your IP address
|
||||
when this occurs. These technologies are used to measure the
|
||||
effectiveness of their advertising campaigns and/or to personalize the
|
||||
advertising content that you see on websites that you visit.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that OYS BAB Test has no access to or control over these cookies that
|
||||
are used by third-party advertisers.
|
||||
</p>
|
||||
<p>
|
||||
Note that Undock has no access to or control over these cookies that
|
||||
are used by third-party advertisers.
|
||||
</p>
|
||||
|
||||
<h2>Third Party Privacy Policies</h2>
|
||||
<h2>Third Party Privacy Policies</h2>
|
||||
|
||||
<p>
|
||||
OYS BAB Test's Privacy Policy does not apply to other advertisers or
|
||||
websites. Thus, we are advising you to consult the respective Privacy
|
||||
Policies of these third-party ad servers for more detailed information. It
|
||||
may include their practices and instructions about how to opt-out of
|
||||
certain options.
|
||||
</p>
|
||||
<p>
|
||||
Undock's Privacy Policy does not apply to other advertisers or
|
||||
websites. Thus, we are advising you to consult the respective Privacy
|
||||
Policies of these third-party ad servers for more detailed
|
||||
information. It may include their practices and instructions about how
|
||||
to opt-out of certain options.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can choose to disable cookies through your individual browser options.
|
||||
To know more detailed information about cookie management with specific
|
||||
web browsers, it can be found at the browsers' respective websites.
|
||||
</p>
|
||||
<p>
|
||||
You can choose to disable cookies through your individual browser
|
||||
options. To know more detailed information about cookie management
|
||||
with specific web browsers, it can be found at the browsers'
|
||||
respective websites.
|
||||
</p>
|
||||
|
||||
<h2>Children's Information</h2>
|
||||
<h2>Children's Information</h2>
|
||||
|
||||
<p>
|
||||
Another part of our priority is adding protection for children while using
|
||||
the internet. We encourage parents and guardians to observe, participate
|
||||
in, and/or monitor and guide their online activity.
|
||||
</p>
|
||||
<p>
|
||||
Another part of our priority is adding protection for children while
|
||||
using the internet. We encourage parents and guardians to observe,
|
||||
participate in, and/or monitor and guide their online activity.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
OYS BAB Test does not knowingly collect any Personal Identifiable
|
||||
Information from children under the age of 13. If you think that your
|
||||
child provided this kind of information on our website, we strongly
|
||||
encourage you to contact us immediately and we will do our best efforts to
|
||||
promptly remove such information from our records.
|
||||
</p>
|
||||
<p>
|
||||
Undock does not knowingly collect any Personal Identifiable
|
||||
Information from children under the age of 13. If you think that your
|
||||
child provided this kind of information on our website, we strongly
|
||||
encourage you to contact us immediately and we will do our best
|
||||
efforts to promptly remove such information from our records.
|
||||
</p>
|
||||
|
||||
<h2>Online Privacy Policy Only</h2>
|
||||
<h2>Online Privacy Policy Only</h2>
|
||||
|
||||
<p>
|
||||
Our Privacy Policy applies only to our online activities and is valid for
|
||||
visitors to our website with regards to the information that they shared
|
||||
and/or collect in OYS BAB Test. This policy is not applicable to any
|
||||
information collected offline or via channels other than this website.
|
||||
</p>
|
||||
<p>
|
||||
Our Privacy Policy applies only to our online activities and is valid
|
||||
for visitors to our website with regards to the information that they
|
||||
shared and/or collect in Undock. This policy is not applicable to any
|
||||
information collected offline or via channels other than this website.
|
||||
</p>
|
||||
|
||||
<h2>Consent</h2>
|
||||
<h2>Consent</h2>
|
||||
|
||||
<p>
|
||||
By using our website, you hereby consent to our Privacy Policy and agree
|
||||
to its terms.
|
||||
</p>
|
||||
</q-page>
|
||||
<p>
|
||||
By using our website, you hereby consent to our Privacy Policy and
|
||||
agree to its
|
||||
<a href="/terms-of-service">terms</a>
|
||||
.
|
||||
</p>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
<template>
|
||||
<toolbar-component pageTitle="Member Profile" />
|
||||
<q-page padding>
|
||||
<q-list bordered>
|
||||
<q-page
|
||||
padding
|
||||
class="row">
|
||||
<q-list class="col-sm-4 col-12">
|
||||
<q-separator />
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-avatar icon="person" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
{{ authStore.currentUser?.name }}
|
||||
<q-item-label caption>Name</q-item-label>
|
||||
<q-input
|
||||
filled
|
||||
v-model="newName"
|
||||
@keydown.enter.prevent="editName"
|
||||
v-if="newName !== undefined" />
|
||||
<div v-else>
|
||||
{{ authStore.currentUser?.name }}
|
||||
</div>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-btn
|
||||
square
|
||||
@click="editName"
|
||||
:icon="newName !== undefined ? 'check' : 'edit'" />
|
||||
<q-btn
|
||||
v-if="newName !== undefined"
|
||||
square
|
||||
color="negative"
|
||||
@click="newName = undefined"
|
||||
icon="cancel" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-avatar icon="email" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label caption>E-mail</q-item-label>
|
||||
{{ authStore.currentUser?.email }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
@@ -17,15 +47,27 @@
|
||||
<q-item-section>
|
||||
<q-item-label overline>Certifications</q-item-label>
|
||||
<div>
|
||||
<q-chip square icon="verified" color="green" text-color="white"
|
||||
>J/27</q-chip
|
||||
>
|
||||
<q-chip square icon="verified" color="blue" text-color="white"
|
||||
>Capri25</q-chip
|
||||
>
|
||||
<q-chip square icon="verified" color="grey-9" text-color="white"
|
||||
>Night</q-chip
|
||||
>
|
||||
<q-chip
|
||||
square
|
||||
icon="verified"
|
||||
color="green"
|
||||
text-color="white">
|
||||
J/27
|
||||
</q-chip>
|
||||
<q-chip
|
||||
square
|
||||
icon="verified"
|
||||
color="blue"
|
||||
text-color="white">
|
||||
Capri25
|
||||
</q-chip>
|
||||
<q-chip
|
||||
square
|
||||
icon="verified"
|
||||
color="grey-9"
|
||||
text-color="white">
|
||||
Night
|
||||
</q-chip>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
@@ -36,6 +78,21 @@
|
||||
<script setup lang="ts">
|
||||
import ToolbarComponent from 'src/components/ToolbarComponent.vue';
|
||||
import { useAuthStore } from 'src/stores/auth';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const newName = ref();
|
||||
|
||||
const editName = async () => {
|
||||
if (newName.value) {
|
||||
try {
|
||||
await authStore.updateName(newName.value);
|
||||
newName.value = undefined;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
} else {
|
||||
newName.value = authStore.currentUser?.name || '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -34,58 +34,27 @@
|
||||
@click="resetPw"
|
||||
label="Send Reset Link"
|
||||
color="primary"></q-btn>
|
||||
<!-- <q-btn
|
||||
type="button"
|
||||
@click="register"
|
||||
color="secondary"
|
||||
label="Register"
|
||||
flat
|
||||
></q-btn> -->
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
<q-form
|
||||
@submit="submitNewPw"
|
||||
v-else-if="validResetLink()">
|
||||
<q-card-section class="q-ma-sm">
|
||||
<q-input
|
||||
v-model="password"
|
||||
label="New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<q-input
|
||||
v-model="confirmPassword"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
color="darkblue"
|
||||
:rules="[validatePasswordStrength]"
|
||||
lazy-rules
|
||||
filled></q-input>
|
||||
<div class="text-caption q-py-md">Enter a new password.</div>
|
||||
</q-card-section>
|
||||
<q-card-actions>
|
||||
<q-btn
|
||||
type="submit"
|
||||
label="Reset Password"
|
||||
color="primary"></q-btn>
|
||||
<!-- <q-btn
|
||||
type="button"
|
||||
@click="register"
|
||||
color="secondary"
|
||||
label="Register"
|
||||
flat
|
||||
></q-btn> -->
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
<div v-else-if="validResetLink()">
|
||||
<q-form
|
||||
@submit="submitNewPw"
|
||||
@keydown.enter.prevent="resetPw">
|
||||
<NewPasswordComponent v-model="newPassword" />
|
||||
<q-card-actions>
|
||||
<q-btn
|
||||
type="submit"
|
||||
label="Reset Password"
|
||||
color="primary"></q-btn>
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</div>
|
||||
<q-card
|
||||
v-else
|
||||
class="text-center">
|
||||
<span class="text-h5">Invalid reset link.</span>
|
||||
</q-card>
|
||||
<!-- <q-card-section><GoogleOauthComponent /></q-card-section> -->
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
@@ -112,38 +81,11 @@ import { ref } from 'vue';
|
||||
import { account, resetPassword } from 'boot/appwrite';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Dialog } from 'quasar';
|
||||
// import GoogleOauthComponent from 'src/components/GoogleOauthComponent.vue';
|
||||
import NewPasswordComponent from 'components/NewPasswordComponent.vue';
|
||||
|
||||
const email = ref('');
|
||||
const router = useRouter();
|
||||
const password = ref('');
|
||||
const confirmPassword = ref('');
|
||||
|
||||
const validatePasswordStrength = (val: string) => {
|
||||
const hasUpperCase = /[A-Z]/.test(val);
|
||||
const hasLowerCase = /[a-z]/.test(val);
|
||||
const hasNumbers = /[0-9]/.test(val);
|
||||
const hasNonAlphas = /[\W_]/.test(val);
|
||||
const isValidLength = val.length >= 8;
|
||||
|
||||
return (
|
||||
(hasUpperCase &&
|
||||
hasLowerCase &&
|
||||
hasNumbers &&
|
||||
hasNonAlphas &&
|
||||
isValidLength) ||
|
||||
'Password must be at least 8 characters long and include uppercase, lowercase, number, and special character.'
|
||||
);
|
||||
};
|
||||
|
||||
const validatePasswordsMatch = (val: string) => {
|
||||
return val === password.value || 'Passwords do not match.';
|
||||
};
|
||||
|
||||
function isPasswordResetLink() {
|
||||
const query = router.currentRoute.value.query;
|
||||
return query && query.secret && query.userId && query.expire;
|
||||
}
|
||||
const newPassword = ref();
|
||||
|
||||
function validResetLink(): boolean {
|
||||
const query = router.currentRoute.value.query;
|
||||
@@ -153,27 +95,34 @@ function validResetLink(): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isPasswordResetLink() {
|
||||
const query = router.currentRoute.value.query;
|
||||
return query && query.secret && query.userId && query.expire;
|
||||
}
|
||||
|
||||
function submitNewPw() {
|
||||
const query = router.currentRoute.value.query;
|
||||
if (
|
||||
validatePasswordStrength(password.value) === true &&
|
||||
validatePasswordsMatch(confirmPassword.value) === true
|
||||
) {
|
||||
if (newPassword.value) {
|
||||
account
|
||||
.updateRecovery(
|
||||
query.userId as string,
|
||||
query.secret as string,
|
||||
password.value
|
||||
newPassword.value
|
||||
)
|
||||
.then(() => {
|
||||
Dialog.create({ message: 'Password Changed!' });
|
||||
router.replace('/login');
|
||||
Dialog.create({ message: 'Password Changed!' }).onOk(() =>
|
||||
router.replace('/login')
|
||||
);
|
||||
})
|
||||
.catch((e) =>
|
||||
Dialog.create({
|
||||
message: 'Password change failed! Error: ' + e.message,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
Dialog.create({
|
||||
message: 'Invalid password. Try again',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
87
src/pages/SignupPage.vue
Normal file
87
src/pages/SignupPage.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page class="flex bg-image flex-center">
|
||||
<q-card
|
||||
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }">
|
||||
<q-card-section>
|
||||
<q-img
|
||||
fit="scale-down"
|
||||
src="~assets/oysqn_logo.png" />
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-center q-pt-sm">
|
||||
<div class="col text-h6">Sign Up</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-form>
|
||||
<q-card-section class="q-gutter-md">
|
||||
<q-input
|
||||
v-model="email"
|
||||
label="E-Mail"
|
||||
type="email"
|
||||
color="darkblue"
|
||||
:rules="['email']"
|
||||
filled></q-input>
|
||||
<NewPasswordComponent v-model="password" />
|
||||
<q-card-actions>
|
||||
<q-space />
|
||||
<q-btn
|
||||
type="button"
|
||||
@click="doRegister"
|
||||
label="Sign Up"
|
||||
color="primary"></q-btn>
|
||||
</q-card-actions>
|
||||
</q-card-section>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.bg-image {
|
||||
background-image: url('/src/assets/oys_lighthouse.jpg');
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: center;
|
||||
background-size: cover;
|
||||
/* background-image: linear-gradient(
|
||||
135deg,
|
||||
#ed232a 0%,
|
||||
#ffffff 75%,
|
||||
#14539a 100%
|
||||
); */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useAuthStore } from 'src/stores/auth';
|
||||
import NewPasswordComponent from 'src/components/NewPasswordComponent.vue';
|
||||
import { Dialog } from 'quasar';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { APP_VERSION } from 'src/version.js';
|
||||
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const router = useRouter();
|
||||
|
||||
console.log('version:' + APP_VERSION);
|
||||
|
||||
const doRegister = async () => {
|
||||
if (email.value && password.value) {
|
||||
try {
|
||||
await useAuthStore().register(email.value, password.value);
|
||||
Dialog.create({
|
||||
message: 'Account Created! Now log-in with your e-mail / password.',
|
||||
}).onOk(() => router.replace('/login'));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
Dialog.create({
|
||||
message: 'An error occurred. Please ask for support in Discord',
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,119 +1,128 @@
|
||||
<template>
|
||||
<q-page padding>
|
||||
<h1>Website Terms and Conditions of Use</h1>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page padding>
|
||||
<h1>Website Terms and Conditions of Use</h1>
|
||||
|
||||
<h2>1. Terms</h2>
|
||||
<h2>1. Terms</h2>
|
||||
|
||||
<p>
|
||||
By accessing this Website, accessible from https://bab.toal.ca, you are
|
||||
agreeing to be bound by these Website Terms and Conditions of Use and
|
||||
agree that you are responsible for the agreement with any applicable local
|
||||
laws. If you disagree with any of these terms, you are prohibited from
|
||||
accessing this site. The materials contained in this Website are protected
|
||||
by copyright and trade mark law.
|
||||
</p>
|
||||
<p>
|
||||
By accessing this Website, accessible from https://undock.ca, you are
|
||||
agreeing to be bound by these Website Terms and Conditions of Use and
|
||||
agree that you are responsible for the agreement with any applicable
|
||||
local laws. If you disagree with any of these terms, you are
|
||||
prohibited from accessing this site. The materials contained in this
|
||||
Website are protected by copyright and trade mark law.
|
||||
</p>
|
||||
|
||||
<h2>2. Use License</h2>
|
||||
<h2>2. Use License</h2>
|
||||
|
||||
<p>
|
||||
Permission is granted to temporarily download one copy of the materials on
|
||||
bab.toal.ca's Website for personal, non-commercial transitory viewing
|
||||
only. This is the grant of a license, not a transfer of title, and under
|
||||
this license you may not:
|
||||
</p>
|
||||
<p>
|
||||
Permission is granted to temporarily download one copy of the
|
||||
materials on undock.ca's Website for personal, non-commercial
|
||||
transitory viewing only. This is the grant of a license, not a
|
||||
transfer of title, and under this license you may not:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>modify or copy the materials;</li>
|
||||
<li>
|
||||
use the materials for any commercial purpose or for any public display;
|
||||
</li>
|
||||
<li>
|
||||
attempt to reverse engineer any software contained on bab.toal.ca's
|
||||
Website;
|
||||
</li>
|
||||
<li>
|
||||
remove any copyright or other proprietary notations from the materials;
|
||||
or
|
||||
</li>
|
||||
<li>
|
||||
transferring the materials to another person or "mirror" the materials
|
||||
on any other server.
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>modify or copy the materials;</li>
|
||||
<li>
|
||||
use the materials for any commercial purpose or for any public
|
||||
display;
|
||||
</li>
|
||||
<li>
|
||||
attempt to reverse engineer any software contained on undock.ca's
|
||||
Website;
|
||||
</li>
|
||||
<li>
|
||||
remove any copyright or other proprietary notations from the
|
||||
materials; or
|
||||
</li>
|
||||
<li>
|
||||
transferring the materials to another person or "mirror" the
|
||||
materials on any other server.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
This will let bab.toal.ca to terminate upon violations of any of these
|
||||
restrictions. Upon termination, your viewing right will also be terminated
|
||||
and you should destroy any downloaded materials in your possession whether
|
||||
it is printed or electronic format. These Terms of Service has been
|
||||
created with the help of the
|
||||
<a href="https://www.termsofservicegenerator.net"
|
||||
>Terms Of Service Generator</a
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
This will let undock.ca to terminate upon violations of any of these
|
||||
restrictions. Upon termination, your viewing right will also be
|
||||
terminated and you should destroy any downloaded materials in your
|
||||
possession whether it is printed or electronic format. These Terms of
|
||||
Service has been created with the help of the
|
||||
<a href="https://www.termsofservicegenerator.net">
|
||||
Terms Of Service Generator
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
|
||||
<h2>3. Disclaimer</h2>
|
||||
<h2>3. Disclaimer</h2>
|
||||
|
||||
<p>
|
||||
All the materials on bab.toal.ca's Website are provided "as is".
|
||||
bab.toal.ca makes no warranties, may it be expressed or implied, therefore
|
||||
negates all other warranties. Furthermore, bab.toal.ca does not make any
|
||||
representations concerning the accuracy or reliability of the use of the
|
||||
materials on its Website or otherwise relating to such materials or any
|
||||
sites linked to this Website.
|
||||
</p>
|
||||
<p>
|
||||
All the materials on undock.ca's Website are provided "as is".
|
||||
undock.ca makes no warranties, may it be expressed or implied,
|
||||
therefore negates all other warranties. Furthermore, undock.ca does
|
||||
not make any representations concerning the accuracy or reliability of
|
||||
the use of the materials on its Website or otherwise relating to such
|
||||
materials or any sites linked to this Website.
|
||||
</p>
|
||||
|
||||
<h2>4. Limitations</h2>
|
||||
<h2>4. Limitations</h2>
|
||||
|
||||
<p>
|
||||
bab.toal.ca or its suppliers will not be hold accountable for any damages
|
||||
that will arise with the use or inability to use the materials on
|
||||
bab.toal.ca's Website, even if bab.toal.ca or an authorize representative
|
||||
of this Website has been notified, orally or written, of the possibility
|
||||
of such damage. Some jurisdiction does not allow limitations on implied
|
||||
warranties or limitations of liability for incidental damages, these
|
||||
limitations may not apply to you.
|
||||
</p>
|
||||
<p>
|
||||
undock.ca or its suppliers will not be hold accountable for any
|
||||
damages that will arise with the use or inability to use the materials
|
||||
on undock.ca's Website, even if bab.toal.ca or an authorize
|
||||
representative of this Website has been notified, orally or written,
|
||||
of the possibility of such damage. Some jurisdiction does not allow
|
||||
limitations on implied warranties or limitations of liability for
|
||||
incidental damages, these limitations may not apply to you.
|
||||
</p>
|
||||
|
||||
<h2>5. Revisions and Errata</h2>
|
||||
<h2>5. Revisions and Errata</h2>
|
||||
|
||||
<p>
|
||||
The materials appearing on bab.toal.ca's Website may include technical,
|
||||
typographical, or photographic errors. bab.toal.ca will not promise that
|
||||
any of the materials in this Website are accurate, complete, or current.
|
||||
bab.toal.ca may change the materials contained on its Website at any time
|
||||
without notice. bab.toal.ca does not make any commitment to update the
|
||||
materials.
|
||||
</p>
|
||||
<p>
|
||||
The materials appearing on undock.ca's Website may include technical,
|
||||
typographical, or photographic errors. undock.ca will not promise that
|
||||
any of the materials in this Website are accurate, complete, or
|
||||
current. undock.ca may change the materials contained on its Website
|
||||
at any time without notice. undock.ca does not make any commitment to
|
||||
update the materials.
|
||||
</p>
|
||||
|
||||
<h2>6. Links</h2>
|
||||
<h2>6. Links</h2>
|
||||
|
||||
<p>
|
||||
bab.toal.ca has not reviewed all of the sites linked to its Website and is
|
||||
not responsible for the contents of any such linked site. The presence of
|
||||
any link does not imply endorsement by bab.toal.ca of the site. The use of
|
||||
any linked website is at the user's own risk.
|
||||
</p>
|
||||
<p>
|
||||
undock.ca has not reviewed all of the sites linked to its Website and
|
||||
is not responsible for the contents of any such linked site. The
|
||||
presence of any link does not imply endorsement by undock.ca of the
|
||||
site. The use of any linked website is at the user's own risk.
|
||||
</p>
|
||||
|
||||
<h2>7. Site Terms of Use Modifications</h2>
|
||||
<h2>7. Site Terms of Use Modifications</h2>
|
||||
|
||||
<p>
|
||||
bab.toal.ca may revise these Terms of Use for its Website at any time
|
||||
without prior notice. By using this Website, you are agreeing to be bound
|
||||
by the current version of these Terms and Conditions of Use.
|
||||
</p>
|
||||
<p>
|
||||
undock.ca may revise these Terms of Use for its Website at any time
|
||||
without prior notice. By using this Website, you are agreeing to be
|
||||
bound by the current version of these Terms and Conditions of Use.
|
||||
</p>
|
||||
|
||||
<h2>8. Your Privacy</h2>
|
||||
<h2>8. Your Privacy</h2>
|
||||
|
||||
<p>Please read our Privacy Policy.</p>
|
||||
<p>
|
||||
Please read our
|
||||
<a href="/privacy-policy">Privacy Policy.</a>
|
||||
</p>
|
||||
|
||||
<h2>9. Governing Law</h2>
|
||||
<h2>9. Governing Law</h2>
|
||||
|
||||
<p>
|
||||
Any claim related to bab.toal.ca's Website shall be governed by the laws
|
||||
of ca without regards to its conflict of law provisions.
|
||||
</p>
|
||||
</q-page>
|
||||
<p>
|
||||
Any claim related to undock.ca's Website shall be governed by the laws
|
||||
of ca without regards to its conflict of law provisions.
|
||||
</p>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
@@ -92,7 +92,7 @@ const currentUser = useAuthStore().currentUser;
|
||||
|
||||
const getSortedIntervals = (timestamp: Timestamp, boat?: Boat): Interval[] => {
|
||||
return getAvailableIntervals(timestamp, boat)
|
||||
.concat(boatReservationEvents(timestamp, boat))
|
||||
.value.concat(boatReservationEvents(timestamp, boat))
|
||||
.sort((a, b) => Date.parse(a.start) - Date.parse(b.start));
|
||||
};
|
||||
// Method declarations
|
||||
@@ -134,16 +134,16 @@ const createReservationFromInterval = (interval: Interval | Reservation) => {
|
||||
function handleSwipe({ ...event }) {
|
||||
event.direction === 'right' ? calendar.value?.prev() : calendar.value?.next();
|
||||
}
|
||||
function boatReservationEvents(
|
||||
const boatReservationEvents = (
|
||||
timestamp: Timestamp,
|
||||
resource: Boat | undefined
|
||||
) {
|
||||
if (!resource) return [];
|
||||
): Reservation[] => {
|
||||
if (!resource) return [] as Reservation[];
|
||||
return reservationStore.getReservationsByDate(
|
||||
getDate(timestamp),
|
||||
(resource as Boat).$id
|
||||
);
|
||||
}
|
||||
).value;
|
||||
};
|
||||
function onToday() {
|
||||
calendar.value.moveToToday();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
class="q-pa-none">
|
||||
<q-card
|
||||
clas="q-ma-md"
|
||||
v-if="!futureUserReservations.length">
|
||||
v-if="!reservationStore.futureUserReservations.length">
|
||||
<q-card-section>
|
||||
<div class="text-h6">You don't have any upcoming bookings!</div>
|
||||
<div class="text-h8">Why don't you go make one?</div>
|
||||
@@ -41,7 +41,7 @@
|
||||
</q-card>
|
||||
<div v-else>
|
||||
<div
|
||||
v-for="reservation in futureUserReservations"
|
||||
v-for="reservation in reservationStore.futureUserReservations"
|
||||
:key="reservation.$id">
|
||||
<ReservationCardComponent :modelValue="reservation" />
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
name="past"
|
||||
class="q-pa-none">
|
||||
<div
|
||||
v-for="reservation in pastUserReservations"
|
||||
v-for="reservation in reservationStore.pastUserReservations"
|
||||
:key="reservation.$id">
|
||||
<ReservationCardComponent :modelValue="reservation" />
|
||||
</div>
|
||||
@@ -63,7 +63,7 @@ import { useReservationStore } from 'src/stores/reservation';
|
||||
import ReservationCardComponent from 'src/components/scheduling/ReservationCardComponent.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const { futureUserReservations, pastUserReservations } = useReservationStore();
|
||||
const reservationStore = useReservationStore();
|
||||
|
||||
onMounted(() => useReservationStore().fetchUserReservations());
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
cell-width="150px">
|
||||
<template #day="{ scope }">
|
||||
<div
|
||||
v-if="filteredIntervals(scope.timestamp, scope.resource).length"
|
||||
v-if="
|
||||
filteredIntervals(scope.timestamp, scope.resource).value.length
|
||||
"
|
||||
style="
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -35,10 +37,8 @@
|
||||
font-size: 12px;
|
||||
">
|
||||
<template
|
||||
v-for="block in sortedIntervals(
|
||||
scope.timestamp,
|
||||
scope.resource
|
||||
)"
|
||||
v-for="block in sortedIntervals(scope.timestamp, scope.resource)
|
||||
.value"
|
||||
:key="block.id">
|
||||
<q-chip class="cursor-pointer">
|
||||
{{ date.formatDate(block.start, 'HH:mm') }} -
|
||||
@@ -163,7 +163,7 @@ import {
|
||||
} from '@quasar/quasar-ui-qcalendar';
|
||||
import { Boat, useBoatStore } from 'src/stores/boat';
|
||||
import { useIntervalStore } from 'src/stores/interval';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import type {
|
||||
Interval,
|
||||
IntervalTemplate,
|
||||
@@ -208,8 +208,10 @@ const filteredIntervals = (date: Timestamp, boat: Boat) => {
|
||||
};
|
||||
|
||||
const sortedIntervals = (date: Timestamp, boat: Boat) => {
|
||||
return filteredIntervals(date, boat).sort(
|
||||
(a, b) => Date.parse(a.start) - Date.parse(b.start)
|
||||
return computed(() =>
|
||||
filteredIntervals(date, boat).value.sort(
|
||||
(a, b) => Date.parse(a.start) - Date.parse(b.start)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -293,7 +295,7 @@ function onDrop(
|
||||
overlapped.value = boatsToApply
|
||||
.map((boat) =>
|
||||
intervalsOverlapped(
|
||||
existingIntervals.concat(
|
||||
existingIntervals.value.concat(
|
||||
intervalsFromTemplate(boat, templateId, date)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,163 +1,173 @@
|
||||
<template>
|
||||
<q-page padding>
|
||||
<h1>Privacy Policy for bab.toal.ca</h1>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page padding>
|
||||
<h1>Privacy Policy for Undock</h1>
|
||||
|
||||
<p>
|
||||
At OYS BAB Test, accessible from https://bab.toal.ca, one of our main
|
||||
priorities is the privacy of our visitors. This Privacy Policy document
|
||||
contains types of information that is collected and recorded by OYS BAB
|
||||
Test and how we use it.
|
||||
</p>
|
||||
<p>
|
||||
At Undock, accessible from https://Undock, one of our main priorities
|
||||
is the privacy of our visitors. This Privacy Policy document contains
|
||||
types of information that is collected and recorded by OYS BAB Test
|
||||
and how we use it.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you have additional questions or require more information about our
|
||||
Privacy Policy, do not hesitate to contact us. Our Privacy Policy was
|
||||
generated with the help of
|
||||
<a href="https://www.gdprprivacypolicy.net/"
|
||||
>GDPR Privacy Policy Generator</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
If you have additional questions or require more information about our
|
||||
Privacy Policy, do not hesitate to contact us. Our Privacy Policy was
|
||||
generated with the help of
|
||||
<a href="https://www.gdprprivacypolicy.net/">
|
||||
GDPR Privacy Policy Generator
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2>General Data Protection Regulation (GDPR)</h2>
|
||||
<p>We are a Data Controller of your information.</p>
|
||||
<h2>General Data Protection Regulation (GDPR)</h2>
|
||||
<p>We are a Data Controller of your information.</p>
|
||||
|
||||
<p>
|
||||
bab.toal.ca legal basis for collecting and using the personal information
|
||||
described in this Privacy Policy depends on the Personal Information we
|
||||
collect and the specific context in which we collect the information:
|
||||
</p>
|
||||
<ul>
|
||||
<li>bab.toal.ca needs to perform a contract with you</li>
|
||||
<li>You have given bab.toal.ca permission to do so</li>
|
||||
<li>
|
||||
Processing your personal information is in bab.toal.ca legitimate
|
||||
interests
|
||||
</li>
|
||||
<li>bab.toal.ca needs to comply with the law</li>
|
||||
</ul>
|
||||
<p>
|
||||
Undock legal basis for collecting and using the personal information
|
||||
described in this Privacy Policy depends on the Personal Information
|
||||
we collect and the specific context in which we collect the
|
||||
information:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Undock needs to perform a contract with you</li>
|
||||
<li>You have given Undock permission to do so</li>
|
||||
<li>
|
||||
Processing your personal information is in Undock legitimate
|
||||
interests
|
||||
</li>
|
||||
<li>Undock needs to comply with the law</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
bab.toal.ca will retain your personal information only for as long as is
|
||||
necessary for the purposes set out in this Privacy Policy. We will retain
|
||||
and use your information to the extent necessary to comply with our legal
|
||||
obligations, resolve disputes, and enforce our policies.
|
||||
</p>
|
||||
<p>
|
||||
Undock will retain your personal information only for as long as is
|
||||
necessary for the purposes set out in this Privacy Policy. We will
|
||||
retain and use your information to the extent necessary to comply with
|
||||
our legal obligations, resolve disputes, and enforce our policies.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you are a resident of the European Economic Area (EEA), you have
|
||||
certain data protection rights. If you wish to be informed what Personal
|
||||
Information we hold about you and if you want it to be removed from our
|
||||
systems, please contact us.
|
||||
</p>
|
||||
<p>
|
||||
If you are a resident of the European Economic Area (EEA), you have
|
||||
certain data protection rights. If you wish to be informed what
|
||||
Personal Information we hold about you and if you want it to be
|
||||
removed from our systems, please contact us.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In certain circumstances, you have the following data protection rights:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
The right to access, update or to delete the information we have on you.
|
||||
</li>
|
||||
<li>The right of rectification.</li>
|
||||
<li>The right to object.</li>
|
||||
<li>The right of restriction.</li>
|
||||
<li>The right to data portability</li>
|
||||
<li>The right to withdraw consent</li>
|
||||
</ul>
|
||||
<p>
|
||||
In certain circumstances, you have the following data protection
|
||||
rights:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
The right to access, update or to delete the information we have on
|
||||
you.
|
||||
</li>
|
||||
<li>The right of rectification.</li>
|
||||
<li>The right to object.</li>
|
||||
<li>The right of restriction.</li>
|
||||
<li>The right to data portability</li>
|
||||
<li>The right to withdraw consent</li>
|
||||
</ul>
|
||||
|
||||
<h2>Log Files</h2>
|
||||
<h2>Log Files</h2>
|
||||
|
||||
<p>
|
||||
OYS BAB Test follows a standard procedure of using log files. These files
|
||||
log visitors when they visit websites. All hosting companies do this and a
|
||||
part of hosting services' analytics. The information collected by log
|
||||
files include internet protocol (IP) addresses, browser type, Internet
|
||||
Service Provider (ISP), date and time stamp, referring/exit pages, and
|
||||
possibly the number of clicks. These are not linked to any information
|
||||
that is personally identifiable. The purpose of the information is for
|
||||
analyzing trends, administering the site, tracking users' movement on the
|
||||
website, and gathering demographic information.
|
||||
</p>
|
||||
<p>
|
||||
Undock follows a standard procedure of using log files. These files
|
||||
log visitors when they visit websites. All hosting companies do this
|
||||
and a part of hosting services' analytics. The information collected
|
||||
by log files include internet protocol (IP) addresses, browser type,
|
||||
Internet Service Provider (ISP), date and time stamp, referring/exit
|
||||
pages, and possibly the number of clicks. These are not linked to any
|
||||
information that is personally identifiable. The purpose of the
|
||||
information is for analyzing trends, administering the site, tracking
|
||||
users' movement on the website, and gathering demographic information.
|
||||
</p>
|
||||
|
||||
<h2>Cookies and Web Beacons</h2>
|
||||
<h2>Cookies and Web Beacons</h2>
|
||||
|
||||
<p>
|
||||
Like any other website, OYS BAB Test uses "cookies". These cookies are
|
||||
used to store information including visitors' preferences, and the pages
|
||||
on the website that the visitor accessed or visited. The information is
|
||||
used to optimize the users' experience by customizing our web page content
|
||||
based on visitors' browser type and/or other information.
|
||||
</p>
|
||||
<p>
|
||||
Like any other website, Undock uses "cookies". These cookies are used
|
||||
to store information including visitors' preferences, and the pages on
|
||||
the website that the visitor accessed or visited. The information is
|
||||
used to optimize the users' experience by customizing our web page
|
||||
content based on visitors' browser type and/or other information.
|
||||
</p>
|
||||
|
||||
<h2>Privacy Policies</h2>
|
||||
<h2>Privacy Policies</h2>
|
||||
|
||||
<P
|
||||
>You may consult this list to find the Privacy Policy for each of the
|
||||
advertising partners of OYS BAB Test.</P
|
||||
>
|
||||
<p>
|
||||
You may consult this list to find the Privacy Policy for each of the
|
||||
advertising partners of Undock.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Third-party ad servers or ad networks uses technologies like cookies,
|
||||
JavaScript, or Web Beacons that are used in their respective
|
||||
advertisements and links that appear on OYS BAB Test, which are sent
|
||||
directly to users' browser. They automatically receive your IP address
|
||||
when this occurs. These technologies are used to measure the effectiveness
|
||||
of their advertising campaigns and/or to personalize the advertising
|
||||
content that you see on websites that you visit.
|
||||
</p>
|
||||
<p>
|
||||
Third-party ad servers or ad networks uses technologies like cookies,
|
||||
JavaScript, or Web Beacons that are used in their respective
|
||||
advertisements and links that appear on Undock, which are sent
|
||||
directly to users' browser. They automatically receive your IP address
|
||||
when this occurs. These technologies are used to measure the
|
||||
effectiveness of their advertising campaigns and/or to personalize the
|
||||
advertising content that you see on websites that you visit.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that OYS BAB Test has no access to or control over these cookies that
|
||||
are used by third-party advertisers.
|
||||
</p>
|
||||
<p>
|
||||
Note that Undock has no access to or control over these cookies that
|
||||
are used by third-party advertisers.
|
||||
</p>
|
||||
|
||||
<h2>Third Party Privacy Policies</h2>
|
||||
<h2>Third Party Privacy Policies</h2>
|
||||
|
||||
<p>
|
||||
OYS BAB Test's Privacy Policy does not apply to other advertisers or
|
||||
websites. Thus, we are advising you to consult the respective Privacy
|
||||
Policies of these third-party ad servers for more detailed information. It
|
||||
may include their practices and instructions about how to opt-out of
|
||||
certain options.
|
||||
</p>
|
||||
<p>
|
||||
Undock's Privacy Policy does not apply to other advertisers or
|
||||
websites. Thus, we are advising you to consult the respective Privacy
|
||||
Policies of these third-party ad servers for more detailed
|
||||
information. It may include their practices and instructions about how
|
||||
to opt-out of certain options.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can choose to disable cookies through your individual browser options.
|
||||
To know more detailed information about cookie management with specific
|
||||
web browsers, it can be found at the browsers' respective websites.
|
||||
</p>
|
||||
<p>
|
||||
You can choose to disable cookies through your individual browser
|
||||
options. To know more detailed information about cookie management
|
||||
with specific web browsers, it can be found at the browsers'
|
||||
respective websites.
|
||||
</p>
|
||||
|
||||
<h2>Children's Information</h2>
|
||||
<h2>Children's Information</h2>
|
||||
|
||||
<p>
|
||||
Another part of our priority is adding protection for children while using
|
||||
the internet. We encourage parents and guardians to observe, participate
|
||||
in, and/or monitor and guide their online activity.
|
||||
</p>
|
||||
<p>
|
||||
Another part of our priority is adding protection for children while
|
||||
using the internet. We encourage parents and guardians to observe,
|
||||
participate in, and/or monitor and guide their online activity.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
OYS BAB Test does not knowingly collect any Personal Identifiable
|
||||
Information from children under the age of 13. If you think that your
|
||||
child provided this kind of information on our website, we strongly
|
||||
encourage you to contact us immediately and we will do our best efforts to
|
||||
promptly remove such information from our records.
|
||||
</p>
|
||||
<p>
|
||||
Undock does not knowingly collect any Personal Identifiable
|
||||
Information from children under the age of 13. If you think that your
|
||||
child provided this kind of information on our website, we strongly
|
||||
encourage you to contact us immediately and we will do our best
|
||||
efforts to promptly remove such information from our records.
|
||||
</p>
|
||||
|
||||
<h2>Online Privacy Policy Only</h2>
|
||||
<h2>Online Privacy Policy Only</h2>
|
||||
|
||||
<p>
|
||||
Our Privacy Policy applies only to our online activities and is valid for
|
||||
visitors to our website with regards to the information that they shared
|
||||
and/or collect in OYS BAB Test. This policy is not applicable to any
|
||||
information collected offline or via channels other than this website.
|
||||
</p>
|
||||
<p>
|
||||
Our Privacy Policy applies only to our online activities and is valid
|
||||
for visitors to our website with regards to the information that they
|
||||
shared and/or collect in Undock. This policy is not applicable to any
|
||||
information collected offline or via channels other than this website.
|
||||
</p>
|
||||
|
||||
<h2>Consent</h2>
|
||||
<h2>Consent</h2>
|
||||
|
||||
<p>
|
||||
By using our website, you hereby consent to our Privacy Policy and agree
|
||||
to its terms.
|
||||
</p>
|
||||
</q-page>
|
||||
<p>
|
||||
By using our website, you hereby consent to our Privacy Policy and
|
||||
agree to its terms.
|
||||
</p>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script
|
||||
setup
|
||||
lang="ts"></script>
|
||||
|
||||
@@ -49,6 +49,10 @@ export default route(function (/* { store, ssrContext } */) {
|
||||
return next('/login');
|
||||
}
|
||||
|
||||
if (to.name === 'login' && currentUser) {
|
||||
return next('/');
|
||||
}
|
||||
|
||||
if (requiredRoles) {
|
||||
if (!currentUser) {
|
||||
return next('/login');
|
||||
|
||||
@@ -24,7 +24,7 @@ export const links = <Link[]>[
|
||||
to: '/profile',
|
||||
icon: 'account_circle',
|
||||
front_links: false,
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'Boats',
|
||||
|
||||
@@ -168,14 +168,14 @@ const routes: RouteRecordRaw[] = [
|
||||
publicRoute: true,
|
||||
},
|
||||
},
|
||||
// {
|
||||
// path: '/register',
|
||||
// component: () => import('pages/RegisterPage.vue'),
|
||||
// name: 'register'
|
||||
// meta: {
|
||||
// accountRoute: true,
|
||||
// }
|
||||
// },
|
||||
{
|
||||
path: '/signup',
|
||||
component: () => import('pages/SignupPage.vue'),
|
||||
name: 'signup',
|
||||
meta: {
|
||||
publicRoute: true,
|
||||
},
|
||||
},
|
||||
// Always leave this as last one,
|
||||
// but you can also remove it
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@ import { defineStore } from 'pinia';
|
||||
import { ID, account, functions, teams } from 'boot/appwrite';
|
||||
import { ExecutionMethod, OAuthProvider, type Models } from 'appwrite';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useBoatStore } from './boat';
|
||||
import { useReservationStore } from './reservation';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const currentUser = ref<Models.User<Models.Preferences> | null>(null);
|
||||
@@ -14,6 +16,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
try {
|
||||
currentUser.value = await account.get();
|
||||
currentUserTeams.value = await teams.list();
|
||||
await useBoatStore().fetchBoats();
|
||||
await useReservationStore().fetchUserReservations();
|
||||
} catch {
|
||||
currentUser.value = null;
|
||||
currentUserTeams.value = null;
|
||||
@@ -41,13 +45,31 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
await init();
|
||||
}
|
||||
|
||||
async function createTokenSession(email: string) {
|
||||
return await account.createEmailToken(ID.unique(), email);
|
||||
}
|
||||
|
||||
async function googleLogin() {
|
||||
account.createOAuth2Session(
|
||||
await account.createOAuth2Session(
|
||||
OAuthProvider.Google,
|
||||
'https://bab.toal.ca/',
|
||||
'https://bab.toal.ca/#/login'
|
||||
'https://oys.undock.ca',
|
||||
'https://oys.undock.ca/login'
|
||||
);
|
||||
currentUser.value = await account.get();
|
||||
await init();
|
||||
}
|
||||
|
||||
async function discordLogin() {
|
||||
await account.createOAuth2Session(
|
||||
OAuthProvider.Discord,
|
||||
'https://oys.undock.ca',
|
||||
'https://oys.undock.ca/login'
|
||||
);
|
||||
await init();
|
||||
}
|
||||
|
||||
async function tokenLogin(userId: string, token: string) {
|
||||
await account.createSession(userId, token);
|
||||
await init();
|
||||
}
|
||||
|
||||
function getUserNameById(id: string | undefined | null): string {
|
||||
@@ -81,13 +103,22 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
return account.deleteSession('current').then((currentUser.value = null));
|
||||
}
|
||||
|
||||
async function updateName(name: string) {
|
||||
await account.updateName(name);
|
||||
currentUser.value = await account.get();
|
||||
}
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
getUserNameById,
|
||||
hasRequiredRole,
|
||||
register,
|
||||
updateName,
|
||||
login,
|
||||
googleLogin,
|
||||
discordLogin,
|
||||
createTokenSession,
|
||||
tokenLogin,
|
||||
logout,
|
||||
init,
|
||||
};
|
||||
|
||||
@@ -2,25 +2,44 @@ import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Boat } from './boat';
|
||||
import { Timestamp, today } from '@quasar/quasar-ui-qcalendar';
|
||||
|
||||
import { Interval, IntervalRecord } from './schedule.types';
|
||||
import { Interval } from './schedule.types';
|
||||
import { AppwriteIds, databases } from 'src/boot/appwrite';
|
||||
import { ID, Query } from 'appwrite';
|
||||
import { useReservationStore } from './reservation';
|
||||
import { LoadingTypes } from 'src/utils/misc';
|
||||
import { useRealtimeStore } from './realtime';
|
||||
|
||||
export const useIntervalStore = defineStore('interval', () => {
|
||||
// TODO: Implement functions to dynamically pull this data.
|
||||
const intervals = ref<Map<string, Interval>>(new Map());
|
||||
const intervalDates = ref<IntervalRecord>({});
|
||||
const reservationStore = useReservationStore();
|
||||
const intervals = ref(new Map<string, Interval>()); // Intervals by DocID
|
||||
const dateStatus = ref(new Map<string, LoadingTypes>()); // State of load by date
|
||||
|
||||
const selectedDate = ref<string>(today());
|
||||
|
||||
const getIntervals = (date: Timestamp | string, boat?: Boat): Interval[] => {
|
||||
const reservationStore = useReservationStore();
|
||||
|
||||
const realtimeStore = useRealtimeStore();
|
||||
|
||||
realtimeStore.register(
|
||||
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.interval}.documents`,
|
||||
(response) => {
|
||||
const payload = response.payload as Interval;
|
||||
if (!payload.$id) return;
|
||||
if (
|
||||
response.events.includes('databases.*.collections.*.documents.*.delete')
|
||||
) {
|
||||
intervals.value.delete(payload.$id);
|
||||
} else {
|
||||
intervals.value.set(payload.$id, payload);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const getIntervals = (date: Timestamp | string, boat?: Boat) => {
|
||||
const searchDate = typeof date === 'string' ? date : date.date;
|
||||
const dayStart = new Date(searchDate + 'T00:00');
|
||||
const dayEnd = new Date(searchDate + 'T23:59');
|
||||
if (!intervalDates.value[searchDate]) {
|
||||
intervalDates.value[searchDate] = 'pending';
|
||||
if (dateStatus.value.get(searchDate) === undefined) {
|
||||
dateStatus.value.set(searchDate, 'pending');
|
||||
fetchIntervals(searchDate);
|
||||
}
|
||||
return computed(() => {
|
||||
@@ -32,22 +51,19 @@ export const useIntervalStore = defineStore('interval', () => {
|
||||
const matchesBoat = boat ? boat.$id === interval.resource : true;
|
||||
return isWithinDay && matchesBoat;
|
||||
});
|
||||
}).value;
|
||||
});
|
||||
};
|
||||
|
||||
const getAvailableIntervals = (
|
||||
date: Timestamp | string,
|
||||
boat?: Boat
|
||||
): Interval[] => {
|
||||
return computed(() => {
|
||||
return getIntervals(date, boat).filter((interval) => {
|
||||
const getAvailableIntervals = (date: Timestamp | string, boat?: Boat) => {
|
||||
return computed(() =>
|
||||
getIntervals(date, boat).value.filter((interval) => {
|
||||
return !reservationStore.isResourceTimeOverlapped(
|
||||
interval.resource,
|
||||
new Date(interval.start),
|
||||
new Date(interval.end)
|
||||
);
|
||||
});
|
||||
}).value;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
async function fetchInterval(id: string): Promise<Interval> {
|
||||
@@ -78,11 +94,11 @@ export const useIntervalStore = defineStore('interval', () => {
|
||||
response.documents.forEach((d) =>
|
||||
intervals.value.set(d.$id, d as Interval)
|
||||
);
|
||||
intervalDates.value[dateString] = 'loaded';
|
||||
dateStatus.value.set(dateString, 'loaded');
|
||||
console.info(`Loaded ${response.documents.length} intervals from server`);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch intervals', error);
|
||||
intervalDates.value[dateString] = 'error';
|
||||
dateStatus.value.set(dateString, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,5 +156,6 @@ export const useIntervalStore = defineStore('interval', () => {
|
||||
updateInterval,
|
||||
deleteInterval,
|
||||
selectedDate,
|
||||
intervals,
|
||||
};
|
||||
});
|
||||
|
||||
21
src/stores/realtime.ts
Normal file
21
src/stores/realtime.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { client } from 'src/boot/appwrite';
|
||||
import { Interval } from './schedule.types';
|
||||
import { ref } from 'vue';
|
||||
import { RealtimeResponseEvent } from 'appwrite';
|
||||
|
||||
export const useRealtimeStore = defineStore('realtime', () => {
|
||||
const subscriptions = ref<Map<string, () => void>>(new Map());
|
||||
|
||||
const register = (
|
||||
channel: string,
|
||||
fn: (response: RealtimeResponseEvent<Interval>) => void
|
||||
) => {
|
||||
if (subscriptions.value.has(channel)) return; // Already subscribed. But maybe different callback fn?
|
||||
subscriptions.value.set(channel, client.subscribe(channel, fn));
|
||||
};
|
||||
|
||||
return {
|
||||
register,
|
||||
};
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import type { Reservation } from './schedule.types';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { ComputedRef, computed, reactive } from 'vue';
|
||||
import { AppwriteIds, databases } from 'src/boot/appwrite';
|
||||
import { ID, Query } from 'appwrite';
|
||||
import { date, useQuasar } from 'quasar';
|
||||
@@ -8,15 +8,37 @@ import { Timestamp, parseDate, today } from '@quasar/quasar-ui-qcalendar';
|
||||
import { LoadingTypes } from 'src/utils/misc';
|
||||
import { useAuthStore } from './auth';
|
||||
import { isPast } from 'src/utils/schedule';
|
||||
import { useRealtimeStore } from './realtime';
|
||||
|
||||
export const useReservationStore = defineStore('reservation', () => {
|
||||
const reservations = ref<Map<string, Reservation>>(new Map());
|
||||
const datesLoaded = ref<Record<string, LoadingTypes>>({});
|
||||
const userReservations = ref<Map<string, Reservation>>(new Map());
|
||||
// TODO: Come up with a better way of storing reservations by date & reservations for user
|
||||
const reservations = reactive<Map<string, Reservation>>(new Map());
|
||||
const datesLoaded = reactive<Record<string, LoadingTypes>>({});
|
||||
const userReservations = reactive<Map<string, Reservation>>(new Map());
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const $q = useQuasar();
|
||||
const realtimeStore = useRealtimeStore();
|
||||
|
||||
realtimeStore.register(
|
||||
`databases.${AppwriteIds.databaseId}.collections.${AppwriteIds.collection.reservation}.documents`,
|
||||
(response) => {
|
||||
const payload = response.payload as Reservation;
|
||||
if (payload.$id) {
|
||||
if (
|
||||
response.events.includes(
|
||||
'databases.*.collections.*.documents.*.delete'
|
||||
)
|
||||
) {
|
||||
reservations.delete(payload.$id);
|
||||
userReservations.delete(payload.$id);
|
||||
} else {
|
||||
reservations.set(payload.$id, payload);
|
||||
if (payload.user === authStore.currentUser?.$id)
|
||||
userReservations.set(payload.$id, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
// Fetch reservations for a specific date range
|
||||
const fetchReservationsForDateRange = async (
|
||||
start: string = today(),
|
||||
@@ -40,7 +62,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
);
|
||||
|
||||
response.documents.forEach((d) =>
|
||||
reservations.value.set(d.$id, d as Reservation)
|
||||
reservations.set(d.$id, d as Reservation)
|
||||
);
|
||||
setDateLoaded(startDate, endDate, 'loaded');
|
||||
} catch (error) {
|
||||
@@ -81,8 +103,8 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
reservation
|
||||
);
|
||||
}
|
||||
reservations.value.set(response.$id, response as Reservation);
|
||||
userReservations.value.set(response.$id, response as Reservation);
|
||||
reservations.set(response.$id, response as Reservation);
|
||||
userReservations.set(response.$id, response as Reservation);
|
||||
console.info('Reservation booked: ', response);
|
||||
return response as Reservation;
|
||||
} catch (e) {
|
||||
@@ -95,14 +117,8 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
reservation: string | Reservation | null | undefined
|
||||
) => {
|
||||
if (!reservation) return false;
|
||||
let id;
|
||||
if (typeof reservation === 'string') {
|
||||
id = reservation;
|
||||
} else if ('$id' in reservation && typeof reservation.$id === 'string') {
|
||||
id = reservation.$id;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const id = typeof reservation === 'string' ? reservation : reservation.$id;
|
||||
if (!id) return false;
|
||||
|
||||
const status = $q.notify({
|
||||
color: 'secondary',
|
||||
@@ -120,8 +136,8 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
AppwriteIds.collection.reservation,
|
||||
id
|
||||
);
|
||||
reservations.value.delete(id);
|
||||
userReservations.value.delete(id);
|
||||
reservations.delete(id);
|
||||
userReservations.delete(id);
|
||||
console.info(`Deleted reservation: ${id}`);
|
||||
status({
|
||||
color: 'warning',
|
||||
@@ -146,7 +162,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
if (start > end) return [];
|
||||
let curDate = start;
|
||||
while (curDate < end) {
|
||||
datesLoaded.value[(parseDate(curDate) as Timestamp).date] = state;
|
||||
datesLoaded[(parseDate(curDate) as Timestamp).date] = state;
|
||||
curDate = date.addToDate(curDate, { days: 1 });
|
||||
}
|
||||
};
|
||||
@@ -157,8 +173,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
const unloaded = [];
|
||||
while (curDate < end) {
|
||||
const parsedDate = (parseDate(curDate) as Timestamp).date;
|
||||
if (datesLoaded.value[parsedDate] === undefined)
|
||||
unloaded.push(parsedDate);
|
||||
if (datesLoaded[parsedDate] === undefined) unloaded.push(parsedDate);
|
||||
curDate = date.addToDate(curDate, { days: 1 });
|
||||
}
|
||||
return unloaded;
|
||||
@@ -168,15 +183,15 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
const getReservationsByDate = (
|
||||
searchDate: string,
|
||||
boat?: string
|
||||
): Reservation[] => {
|
||||
if (!datesLoaded.value[searchDate]) {
|
||||
): ComputedRef<Reservation[]> => {
|
||||
if (!datesLoaded[searchDate]) {
|
||||
fetchReservationsForDateRange(searchDate);
|
||||
}
|
||||
const dayStart = new Date(searchDate + 'T00:00');
|
||||
const dayEnd = new Date(searchDate + 'T23:59');
|
||||
|
||||
return computed(() => {
|
||||
return Array.from(reservations.value.values()).filter((reservation) => {
|
||||
return Array.from(reservations.values()).filter((reservation) => {
|
||||
const reservationStart = new Date(reservation.start);
|
||||
const reservationEnd = new Date(reservation.end);
|
||||
|
||||
@@ -185,7 +200,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
const matchesBoat = boat ? boat === reservation.resource : true;
|
||||
return isWithinDay && matchesBoat;
|
||||
});
|
||||
}).value;
|
||||
});
|
||||
};
|
||||
|
||||
// Get conflicting reservations for a resource within a time range
|
||||
@@ -194,7 +209,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
start: Date,
|
||||
end: Date
|
||||
): Reservation[] => {
|
||||
return Array.from(reservations.value.values()).filter(
|
||||
return Array.from(reservations.values()).filter(
|
||||
(entry) =>
|
||||
entry.resource === resource &&
|
||||
new Date(entry.start) < end &&
|
||||
@@ -229,7 +244,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
[Query.equal('user', authStore.currentUser.$id)]
|
||||
);
|
||||
response.documents.forEach((d) =>
|
||||
userReservations.value.set(d.$id, d as Reservation)
|
||||
userReservations.set(d.$id, d as Reservation)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch reservations for user: ', error);
|
||||
@@ -237,7 +252,7 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
};
|
||||
|
||||
const sortedUserReservations = computed((): Reservation[] =>
|
||||
[...userReservations.value?.values()].sort(
|
||||
[...userReservations.values()].sort(
|
||||
(a, b) => new Date(b.start).getTime() - new Date(a.start).getTime()
|
||||
)
|
||||
);
|
||||
@@ -252,27 +267,6 @@ export const useReservationStore = defineStore('reservation', () => {
|
||||
return sortedUserReservations.value?.filter((b) => isPast(b.end));
|
||||
});
|
||||
|
||||
// Ensure reactivity for computed properties when Map is modified
|
||||
watch(
|
||||
reservations,
|
||||
() => {
|
||||
sortedUserReservations.value;
|
||||
futureUserReservations.value;
|
||||
pastUserReservations.value;
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
userReservations,
|
||||
() => {
|
||||
sortedUserReservations.value;
|
||||
futureUserReservations.value;
|
||||
pastUserReservations.value;
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
return {
|
||||
getReservationsByDate,
|
||||
getReservationById,
|
||||
|
||||
@@ -5,7 +5,7 @@ export const getSampleData = () => [
|
||||
displayName: 'PX',
|
||||
class: 'J/27',
|
||||
year: 1981,
|
||||
imgSrc: '/tmpimg/j27.png',
|
||||
imgSrc: '/tmpimg/projectX.jpg',
|
||||
iconSrc: '/tmpimg/projectx_avatar256.png',
|
||||
bookingAvailable: true,
|
||||
maxPassengers: 8,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Models } from 'appwrite';
|
||||
import { LoadingTypes } from 'src/utils/misc';
|
||||
|
||||
export type StatusTypes = 'tentative' | 'confirmed' | 'pending' | undefined;
|
||||
export type Reservation = Interval & {
|
||||
@@ -29,7 +28,3 @@ export type IntervalTemplate = Partial<Models.Document> & {
|
||||
name: string;
|
||||
timeTuples: TimeTuple[];
|
||||
};
|
||||
|
||||
export interface IntervalRecord {
|
||||
[key: string]: LoadingTypes;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user