Compare commits
No commits in common. "rust-rewrite" and "main" have entirely different histories.
rust-rewri
...
main
3
.envrc
@ -1,3 +0,0 @@
|
|||||||
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
|
|
||||||
|
|
||||||
use devenv
|
|
35
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
name: Build and Deploy
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: catthehacker/ubuntu:act-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Nodejs
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
- run: npm install
|
||||||
|
working-directory: ./frontend
|
||||||
|
- run: npm run build
|
||||||
|
working-directory: ./frontend
|
||||||
|
|
||||||
|
- uses: docker/setup-qemu-action@v2
|
||||||
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: gitea.xeovalyte.dev
|
||||||
|
username: ${{ secrets.USERNAME }}
|
||||||
|
password: ${{ secrets.PASSWORD }}
|
||||||
|
|
||||||
|
- run: docker buildx build -t gitea.xeovalyte.dev/xeovalyte/wrbapp:latest-arm --load --platform=linux/arm64 .
|
||||||
|
- run: docker push gitea.xeovalyte.dev/xeovalyte/wrbapp:latest-arm
|
||||||
|
|
||||||
|
- run: docker buildx build -t gitea.xeovalyte.dev/xeovalyte/wrbapp:latest --load --platform=linux/amd64 .
|
||||||
|
- run: docker push gitea.xeovalyte.dev/xeovalyte/wrbapp:latest
|
23
.gitignore
vendored
@ -1,23 +0,0 @@
|
|||||||
# Generated by Cargo
|
|
||||||
# will have compiled files and executables
|
|
||||||
/target/
|
|
||||||
/dist/
|
|
||||||
/static/
|
|
||||||
/.dioxus/
|
|
||||||
database/
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
# Node
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
# Devenv
|
|
||||||
.devenv*
|
|
||||||
devenv.local.nix
|
|
||||||
|
|
||||||
# Direnv
|
|
||||||
.direnv
|
|
||||||
|
|
||||||
# Pre commit
|
|
||||||
.pre-commit-config.yaml
|
|
BIN
Assets/addtohomescreen.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
Assets/done.jpeg
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
Assets/mainview.jpeg
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
Assets/options.jpeg
Normal file
After Width: | Height: | Size: 104 KiB |
5927
Cargo.lock
generated
33
Cargo.toml
@ -1,33 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "wrbapp"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["xeovalyte <me+gitea@xeovalyte.dev>"]
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
|
||||||
|
|
||||||
dioxus = { version = "0.5", features = ["fullstack", "router"] }
|
|
||||||
web-sys = { version = "0.3.70", features = ["Window", "Location"] }
|
|
||||||
|
|
||||||
tokio = { version = "1.38", features = ["macros", "rt-multi-thread"], optional = true }
|
|
||||||
axum = { version = "0.7", optional = true }
|
|
||||||
axum-extra = { version = "0.9", features = ["cookie"], optional = true }
|
|
||||||
time = { version = "0.3", optional = true }
|
|
||||||
once_cell = { version = "1.19", optional = true }
|
|
||||||
surrealdb = { version = "2.0", optional = true }
|
|
||||||
thiserror = { version = "1.0" }
|
|
||||||
|
|
||||||
csv = { version = "1.3", optional = true }
|
|
||||||
|
|
||||||
# Debug
|
|
||||||
tracing = "0.1"
|
|
||||||
dioxus-logger = "0.5"
|
|
||||||
manganis = "0.2"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
server = [ "dioxus/axum", "tokio", "axum", "axum-extra", "time", "once_cell", "surrealdb", "csv" ]
|
|
||||||
web = ["dioxus/web"]
|
|
45
Dioxus.toml
@ -1,45 +0,0 @@
|
|||||||
[application]
|
|
||||||
|
|
||||||
# App (Project) Name
|
|
||||||
name = "wrbapp"
|
|
||||||
|
|
||||||
# Dioxus App Default Platform
|
|
||||||
# desktop, web
|
|
||||||
default_platform = "web"
|
|
||||||
|
|
||||||
# `build` & `serve` dist path
|
|
||||||
out_dir = "dist"
|
|
||||||
|
|
||||||
# resource (assets) file folder
|
|
||||||
asset_dir = "public"
|
|
||||||
|
|
||||||
[web.app]
|
|
||||||
|
|
||||||
# HTML title tag content
|
|
||||||
title = "wrbapp"
|
|
||||||
|
|
||||||
[web.watcher]
|
|
||||||
|
|
||||||
# when watcher trigger, regenerate the `index.html`
|
|
||||||
reload_html = true
|
|
||||||
|
|
||||||
# which files or dirs will be watcher monitoring
|
|
||||||
watch_path = ["src", "assets"]
|
|
||||||
|
|
||||||
# include `assets` in web platform
|
|
||||||
[web.resource]
|
|
||||||
|
|
||||||
# CSS style file
|
|
||||||
|
|
||||||
style = []
|
|
||||||
|
|
||||||
# Javascript code file
|
|
||||||
script = []
|
|
||||||
|
|
||||||
[web.resource.dev]
|
|
||||||
|
|
||||||
# Javascript code file
|
|
||||||
# serve: [dev-server] only
|
|
||||||
script = []
|
|
||||||
|
|
||||||
style = []
|
|
9
Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM node:18
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY ./frontend/.output .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "node", "server/index.mjs" ]
|
14
README.md
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
1. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
|
|
||||||
2. Install the tailwind css cli: https://tailwindcss.com/docs/installation
|
|
||||||
3. Run the following command in the root of the project to start the tailwind CSS compiler:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx tailwindcss -i ./input.css -o ./assets/tailwind.css --watch
|
|
||||||
```
|
|
||||||
|
|
||||||
Launch the Dioxus Fullstack app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dx serve --platform fullstack
|
|
||||||
```
|
|
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 23 KiB |
3514
assets/tailwind.css
153
devenv.lock
@ -1,153 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"devenv": {
|
|
||||||
"locked": {
|
|
||||||
"dir": "src/modules",
|
|
||||||
"lastModified": 1728113618,
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"rev": "a8495abab31ce52cd45d343caa760046c0c7ee74",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"dir": "src/modules",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "devenv",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fenix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1728196311,
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "26971356e387b5ec0578f52be1bbd82ecf6dbad4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1696426674,
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"pre-commit-hooks",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1709087332,
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1728067476,
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-24.05",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs-stable": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1728067476,
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-24.05",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pre-commit-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"nixpkgs-stable": "nixpkgs-stable"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1728092656,
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"rev": "1211305a5b237771e13fcca0c51e60ad47326a9a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "pre-commit-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"devenv": "devenv",
|
|
||||||
"fenix": "fenix",
|
|
||||||
"nixpkgs": "nixpkgs",
|
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-analyzer-src": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1728064742,
|
|
||||||
"owner": "rust-lang",
|
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"rev": "5982d9c420d0dc90739171829f0d2e9c80d98979",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "rust-lang",
|
|
||||||
"ref": "nightly",
|
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
47
devenv.nix
@ -1,47 +0,0 @@
|
|||||||
{ pkgs, lib, config, inputs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# https://devenv.sh/basics/
|
|
||||||
env.GREET = "devenv";
|
|
||||||
env.LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
|
|
||||||
env.BINDGEN_EXTRA_CLANG_ARGS = "-I ${pkgs.libclang.lib}/lib/clang/17/include";
|
|
||||||
|
|
||||||
# https://devenv.sh/packages/
|
|
||||||
packages = [ pkgs.openssl pkgs.git pkgs.tailwindcss pkgs.watchexec pkgs.libclang pkgs.clang ];
|
|
||||||
|
|
||||||
# https://devenv.sh/scripts/
|
|
||||||
scripts.hello.exec = "echo hello from $GREET";
|
|
||||||
|
|
||||||
enterShell = ''
|
|
||||||
hello
|
|
||||||
git --version
|
|
||||||
'';
|
|
||||||
|
|
||||||
# https://devenv.sh/tests/
|
|
||||||
enterTest = ''
|
|
||||||
echo "Running tests"
|
|
||||||
git --version | grep "2.42.0"
|
|
||||||
'';
|
|
||||||
|
|
||||||
# https://devenv.sh/services/
|
|
||||||
# services.postgres.enable = true;
|
|
||||||
|
|
||||||
# https://devenv.sh/languages/
|
|
||||||
languages.rust.enable = true;
|
|
||||||
languages.rust.channel = "stable";
|
|
||||||
languages.rust.targets = [ "wasm32-unknown-unknown" ];
|
|
||||||
|
|
||||||
languages.javascript.enable = true;
|
|
||||||
languages.javascript.npm.enable = true;
|
|
||||||
languages.javascript.npm.install.enable = true;
|
|
||||||
|
|
||||||
# https://devenv.sh/pre-commit-hooks/
|
|
||||||
# pre-commit.hooks.shellcheck.enable = true;
|
|
||||||
|
|
||||||
# https://devenv.sh/processes/
|
|
||||||
processes.tailwind.exec = "watchexec -e rs ${lib.getExe pkgs.tailwindcss} -i input.css -o assets/tailwind.css";
|
|
||||||
processes.dioxus.exec = "dx serve";
|
|
||||||
processes.surrealdb.exec = "docker compose up";
|
|
||||||
|
|
||||||
# See full reference at https://devenv.sh/reference/options/
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
inputs:
|
|
||||||
nixpkgs:
|
|
||||||
url: github:NixOS/nixpkgs/nixos-24.05
|
|
||||||
fenix:
|
|
||||||
url: github:nix-community/fenix
|
|
||||||
inputs:
|
|
||||||
nixpkgs:
|
|
||||||
follows: nixpkgs
|
|
@ -1,19 +0,0 @@
|
|||||||
services:
|
|
||||||
surrealdb:
|
|
||||||
image: surrealdb/surrealdb:latest
|
|
||||||
container_name: surrealdb-wrbapp
|
|
||||||
ports:
|
|
||||||
- 8000:8000
|
|
||||||
volumes:
|
|
||||||
- ./database:/data
|
|
||||||
user: "${UID}:${GID}"
|
|
||||||
entrypoint:
|
|
||||||
- /surreal
|
|
||||||
- start
|
|
||||||
- --user
|
|
||||||
- "root"
|
|
||||||
- --pass
|
|
||||||
- "root"
|
|
||||||
- --log
|
|
||||||
- debug
|
|
||||||
- rocksdb:/data/database.db
|
|
10
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
node_modules
|
||||||
|
*.log*
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
.output
|
||||||
|
.env
|
||||||
|
dist
|
||||||
|
|
||||||
|
service-account.json
|
42
frontend/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Nuxt 3 Minimal Starter
|
||||||
|
|
||||||
|
Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install the dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install --shamefully-hoist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on http://localhost:3000
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
|
72
frontend/app.vue
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="showInstallGuide !== null && showInstallGuide && host != 'localhost'" class="bg-neutral-1100 dark:bg-neutral-900 h-screen flex flex-col px-5 text-center text-black dark:text-gray-200">
|
||||||
|
<h1 class="font-bold text-3xl text-center mb-10 mt-20 text-primary">Reddingsbrigade Waddinxveen</h1>
|
||||||
|
<p class="mb-10">Om gebruik te maken van de WRB App moet je deze installeren</p>
|
||||||
|
<h2 class="font-bold">Op een iPhone:</h2>
|
||||||
|
<p class="mb-10">
|
||||||
|
Ga naar <a href="https://apps.apple.com/us/app/waddinxveense-reddingsbrigade/id6443636255" class="underline">deze link</a> en download de app via de App Store
|
||||||
|
</p>
|
||||||
|
<h2 class="font-bold">Op een Android:</h2>
|
||||||
|
<ol class="list-decimal list-inside mb-3">
|
||||||
|
<li>Druk op het opties icoon:<Icon size="1.7em" name="ion:md-more" /></li>
|
||||||
|
<li>En kies voor "Toevoegen aan startscherm" of "App installeren"</li>
|
||||||
|
</ol>
|
||||||
|
<i>Als deze optie er niet is, gebruik dan de chrome browser</i>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="userStore.userLoaded">
|
||||||
|
<div v-if="userStore.isAuthenticated" class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex flex-col">
|
||||||
|
<LayoutTopbar />
|
||||||
|
<div class="overflow-y-auto pt-3">
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
|
<LayoutNavbar class="mt-auto" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex flex-col">
|
||||||
|
<Login />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="bg-neutral-100 dark:bg-neutral-900 text-primary h-screen flex justify-center items-center">
|
||||||
|
<div>
|
||||||
|
<Icon size="4em" name="ion:load-c" class="animate-spin" />
|
||||||
|
<h2 class="mt-2 font-bold">Loading...</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Device } from '@capacitor/device';
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const showInstallGuide = ref(null)
|
||||||
|
|
||||||
|
const host = ref(null)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
host.value = window.location.hostname
|
||||||
|
userStore.init()
|
||||||
|
|
||||||
|
Device.getInfo().then(info => {
|
||||||
|
if (info.platform === 'ios') {
|
||||||
|
showInstallGuide.value = false;
|
||||||
|
document.getElementsByClassName('top-right')[0].classList.add('toastios')
|
||||||
|
} else if (info.platform === 'web' && process.client && 'serviceWorker' in navigator && window.isSecureContext) {
|
||||||
|
if (window.matchMedia('(display-mode: standalone)').matches) showInstallGuide.value = false
|
||||||
|
else showInstallGuide.value = true
|
||||||
|
|
||||||
|
registerServiceWorker()
|
||||||
|
} else {
|
||||||
|
showInstallGuide.value = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.body {
|
||||||
|
margin-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastios {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
}
|
||||||
|
</style>
|
37
frontend/assets/css/tailwind.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.item-hover {
|
||||||
|
@apply p-3 cursor-pointer hover:bg-neutral-300 dark:hover:bg-neutral-700 transition-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
@apply p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@apply bg-neutral-200 dark:bg-neutral-800 dark:text-gray-300 text-gray-900 shadow rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
@apply bg-primary hover:bg-orange-700 text-gray-900 font-bold px-3 py-1 shadow rounded transition-all hover:cursor-pointer disabled:opacity-50 disabled:hover:cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
@apply w-full h-1 bg-neutral-250 dark:bg-neutral-850;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
@apply h-4 w-4 text-primary rounded dark:bg-neutral-700 bg-neutral-300 hover:cursor-pointer focus:ring-transparent focus:ring-offset-0 border-none
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
@apply bg-neutral-200 dark:bg-neutral-800 dark:text-gray-300 text-gray-900 shadow rounded border-none focus:ring-primary
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-default {
|
||||||
|
@apply dark:text-gray-300 text-gray-900
|
||||||
|
}
|
||||||
|
}
|
10
frontend/capacitor.config.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"appId": "com.reddingsbrigadewaddinxveen.wrbapp",
|
||||||
|
"appName": "WRB App",
|
||||||
|
"webDir": ".output/public",
|
||||||
|
"bundledWebRuntime": false,
|
||||||
|
"server": {
|
||||||
|
"url": "https://app.reddingsbrigadewaddinxveen.nl/",
|
||||||
|
"cleartext": true
|
||||||
|
}
|
||||||
|
}
|
179
frontend/components/Login.vue
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col justify-center h-screen items-center px-2 pb-20">
|
||||||
|
<h1 class="font-bold text-3xl text-center mb-20">Reddingsbrigade Waddinxveen</h1>
|
||||||
|
<div class="max-w-sm w-full">
|
||||||
|
<form v-if="!creatingAccount" @submit.prevent="submitLoginForm" class="flex flex-col">
|
||||||
|
<label class="font-bold">Email</label>
|
||||||
|
<input v-model="form.email" required="true" placeholder="user@example.com" class="input mb-5" type="email">
|
||||||
|
|
||||||
|
<label class="font-bold">Wachtwoord</label>
|
||||||
|
<input v-model="form.password" required="true" class="input" :type="showPassword ? 'text' : 'password'">
|
||||||
|
<div class="mb-5 mt-1 flex items-center text-default">
|
||||||
|
<input v-model="showPassword" type="checkbox" class="mr-1 checkbox ">
|
||||||
|
<span>Toon wachtwoord</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-wrap justify-between">
|
||||||
|
<input :disabled="disableButtons" type="submit" value="Login" class="btn w-full sm:w-24 mb-1">
|
||||||
|
<button @click="forgotPassword" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Wachtwoord vergeten?</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<i class="mt-10 text-black dark:text-gray-200">Is dit jouw eerste keer inloggen? Gebruik dan als email het email dat gebruikt is bij de inschrijving en het wachtwoord is uw Lidnummmer / Sportlinknummer</i>
|
||||||
|
</form>
|
||||||
|
<form v-else @submit.prevent="submitCreateForm" class="flex flex-col">
|
||||||
|
<h3 class="text-center text-default text-lg mb-5">Account aanmaken voor <br><b>{{ form.email }}</b></h3>
|
||||||
|
<label class="font-bold">Nieuw wachtwoord</label>
|
||||||
|
<input v-model="form.newPassword" required="true" class="input mb-1" :type="showPassword ? 'text' : 'password'">
|
||||||
|
<span class="mb-5 text-default italic text-sm">Minimaal 8 karakters</span>
|
||||||
|
|
||||||
|
<label class="font-bold">Herhaal nieuw wachtwoord</label>
|
||||||
|
<input v-model="form.confirmNewPassword" required="true" class="input" :type="showPassword ? 'text' : 'password'">
|
||||||
|
<div class="mb-5 mt-1 flex items-center text-default">
|
||||||
|
<input v-model="showPassword" type="checkbox" class="mr-1 checkbox ">
|
||||||
|
<span>Toon wachtwoord</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-wrap">
|
||||||
|
<input :disabled="disableButtons" type="submit" value="Account Aanmaken" class="btn w-full sm:w-40 mb-1">
|
||||||
|
<button @click="goBack" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Jij niet? Ga terug</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, sendPasswordResetEmail } from "firebase/auth";
|
||||||
|
import { doc, setDoc, getFirestore } from "firebase/firestore";
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const showPassword = ref(false)
|
||||||
|
const creatingAccount = ref(false)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
|
||||||
|
const db = getFirestore()
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmNewPassword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitLoginForm = () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
signInWithEmailAndPassword(userStore.auth, form.value.email, form.value.password)
|
||||||
|
.then(() => disableButtons.value = false)
|
||||||
|
.catch(async (error) => {
|
||||||
|
|
||||||
|
if (error.code === 'auth/user-not-found') {
|
||||||
|
const { error: err, data } = await useFetch('/api/checkrelatiecode', {
|
||||||
|
method: 'post',
|
||||||
|
body: { email: form.value.email, relatiecode: form.value.password.toUpperCase() }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (err.value) {
|
||||||
|
console.log(err.value)
|
||||||
|
disableButtons.value = false
|
||||||
|
return toast.error('Error tijdens het controleren van relatiecode')
|
||||||
|
}
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
if (data.value.code === 'incorrect') return toast.error('Email, wachtwoord of relatiecode onjuist')
|
||||||
|
else if (data.value.code === 'correct') return creatingAccount.value = true
|
||||||
|
|
||||||
|
} else if (error.code === 'auth/wrong-password') {
|
||||||
|
toast.error('Verkeerde wachtwoord')
|
||||||
|
} else {
|
||||||
|
toast.error('Error met inloggen')
|
||||||
|
}
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
console.log(error.message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitCreateForm = () => {
|
||||||
|
if (form.value.newPassword !== form.value.confirmNewPassword) return toast.error('Wachtwoorden zijn niet hetzelfde');
|
||||||
|
if (form.value.newPassword.length < 8) return toast.error('Wachtwoord heeft te weinig karakters');
|
||||||
|
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
createUserWithEmailAndPassword(userStore.auth, form.value.email, form.value.newPassword)
|
||||||
|
.then(async (userCredential) => {
|
||||||
|
const idToken = await userStore.auth.currentUser.getIdToken(true)
|
||||||
|
const { error, data } = await useFetch('/api/getrelatiecodes', {
|
||||||
|
method: 'post',
|
||||||
|
body: { email: form.value.email, token: idToken }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error.value) {
|
||||||
|
console.log(error.value)
|
||||||
|
disableButtons.value = false
|
||||||
|
return toast.error('Error tijdens het controleren van relatiecode')
|
||||||
|
}
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
if (data.value.code === 'error') return toast.error('Error tijdens maken van account')
|
||||||
|
else if (data.value.code === 'success') {
|
||||||
|
await setDoc(doc(db, "users", userCredential.user.uid), {
|
||||||
|
email: form.value.email,
|
||||||
|
relatiecodes: [form.value.password.toUpperCase()],
|
||||||
|
allRelatiecodes: data.value.relatiecodes,
|
||||||
|
id: userCredential.user.uid,
|
||||||
|
});
|
||||||
|
|
||||||
|
data.value.persons.forEach(person => {
|
||||||
|
if (person.relatiecode === form.value.password.toUpperCase()) {
|
||||||
|
person.checked = true
|
||||||
|
} else {
|
||||||
|
person.checked = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
userStore.userAllPersons = data.value.persons
|
||||||
|
|
||||||
|
if (data.value.relatiecodes.length > 1) {
|
||||||
|
return navigateTo('/settings/config/managerelatiecodes')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const errorCode = error.code;
|
||||||
|
const errorMessage = error.message;
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
|
||||||
|
toast.error('Error tijdens het maken van account')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const forgotPassword = () => {
|
||||||
|
sendPasswordResetEmail(userStore.auth, form.value.email)
|
||||||
|
.then(() => {
|
||||||
|
toast.info('Wachtwoord vergeten email verstuurd!')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const errorCode = error.code;
|
||||||
|
const errorMessage = error.message;
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
|
||||||
|
toast.error('Error tijdens het versturen van het wachtwoord vergeten email')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
creatingAccount.value = false
|
||||||
|
form.value = {
|
||||||
|
email: form.value.email,
|
||||||
|
password: form.value.password,
|
||||||
|
newPassword: '',
|
||||||
|
confirmNewPassword: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
48
frontend/components/layout/Navbar.vue
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="route.meta.key !== 'disable'" class="w-full bg-neutral-200 dark:bg-neutral-800 flex justify-center items-center shadow" :class="platform === 'ios' ? 'navbar' : 'py-3'">
|
||||||
|
<div class="flex text-sm justify-evenly items-center gap-1 w-full max-w-lg dark:text-gray-300 text-gray-900 overflow-x-hidden">
|
||||||
|
<NuxtLink to="/" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path === '/' ? 'text-primary' : ''">
|
||||||
|
<Icon size="1.8em" name="ion:home-outline" />
|
||||||
|
<span>Home</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/news" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/news') ? 'text-primary' : ''">
|
||||||
|
<Icon size="1.8em" name="ion:newspaper-outline" />
|
||||||
|
<span>Nieuws</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/calendar" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path === '/calendar' ? 'text-primary' : ''">
|
||||||
|
<Icon size="1.8em" name="ion:calendar-outline" />
|
||||||
|
<span>Agenda</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink to="/settings" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/settings') ? 'text-primary' : ''">
|
||||||
|
<Icon size="1.8em" name="ion:settings-sharp" />
|
||||||
|
<span>Settings</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<NuxtLink v-if="userStore.userPersons[0] && userStore.userPersons.filter(a => a.wedstrijdteam).length > 0" to="/wedstrijd" class="flex flex-col items-center hover:cursor-pointer drop-shadow" :class="route.path.startsWith('/wedstrijd') ? 'text-primary' : ''">
|
||||||
|
<Icon size="1.8em" name="ion:podium-outline" />
|
||||||
|
<span>Wedstrijd</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Device } from '@capacitor/device';
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const platform = ref(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
Device.getInfo().then(info => {
|
||||||
|
platform.value = info.platform
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.navbar {
|
||||||
|
padding-bottom: calc(env(safe-area-inset-bottom) - 10px);
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
20
frontend/components/layout/Topbar.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="route.meta.key !== 'disable'" class="w-full topbar h-10 bg-neutral-200 dark:bg-neutral-800 flex justify-center items-center shadow px-5 gap-2">
|
||||||
|
<div class="flex justify-evenly items-center gap-3 w-full max-w-xl dark:text-gray-300 text-gray-900">
|
||||||
|
<Icon v-if="route.meta.key === 'back'" size="1.75em" @click="router.back()" class="hover:cursor-pointer" name="ion:arrow-back"/>
|
||||||
|
<h1 class="capitalize font-bold text-xl mr-auto">{{ route.meta.title }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.topbar {
|
||||||
|
padding-top: calc(env(safe-area-inset-top) + 20px);
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
121
frontend/composables/notifications.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { getMessaging, getToken, onMessage } from 'firebase/messaging'
|
||||||
|
import { PushNotifications } from '@capacitor/push-notifications';
|
||||||
|
import { Device } from '@capacitor/device';
|
||||||
|
|
||||||
|
|
||||||
|
export const setupIosNotifications = () => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
// Request permission to use push notifications
|
||||||
|
// iOS will prompt user and return if they granted permission or not
|
||||||
|
// Android will just grant without prompting
|
||||||
|
PushNotifications.requestPermissions().then(result => {
|
||||||
|
if (result.receive === 'granted') {
|
||||||
|
// Register with Apple / Google to receive push via APNS/FCM
|
||||||
|
PushNotifications.register()
|
||||||
|
} else {
|
||||||
|
toast.error('Error tijdens het registrenen van push notificaties')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// On success, we should be able to receive notifications
|
||||||
|
PushNotifications.addListener('registration',
|
||||||
|
async (token) => {
|
||||||
|
// alert('Push registration success, token: ' + token.value);
|
||||||
|
userStore.registrationToken = token
|
||||||
|
|
||||||
|
const { error } = await useFetch('/api/subscribetotopic', {
|
||||||
|
method: 'post',
|
||||||
|
body: { topic: 'all', registrationToken: token.value }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error.value) {
|
||||||
|
console.log(error.value)
|
||||||
|
return toast.error('Error tijdens het krijgen van relateicodes')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Subscribed to topic!')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Some issue with our setup and push will not work
|
||||||
|
PushNotifications.addListener('registrationError',
|
||||||
|
(error) => {
|
||||||
|
toast.error('Error tijdens het registreren van push notificaties')
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show us the notification payload if the app is open on our device
|
||||||
|
PushNotifications.addListener('pushNotificationReceived',
|
||||||
|
(notification) => {
|
||||||
|
|
||||||
|
toast.info(`${notification.title}`, {
|
||||||
|
onClick: () => navigateTo('/news')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Method called when tapping on a notification
|
||||||
|
PushNotifications.addListener('pushNotificationActionPerformed',
|
||||||
|
(notification) => {
|
||||||
|
navigateTo('/news')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setupWebNotifications = () => {
|
||||||
|
const messaging = getMessaging()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
getToken(messaging, { vapidKey: 'BI7l3nyGV6wJcFh7wrwmQ42W7RSXl46bmhXZJmDd4P-0K_JFP0ClTqjO-rr5H5DXBbmVR4kXwxFpUlo_d6cUy4Q' }).then(async (currentToken) => {
|
||||||
|
if (currentToken) {
|
||||||
|
console.log(currentToken)
|
||||||
|
|
||||||
|
const { error} = await useFetch('/api/subscribetotopic', {
|
||||||
|
method: 'post',
|
||||||
|
body: { topic: 'all', registrationToken: currentToken }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error.value) {
|
||||||
|
console.log(error.value)
|
||||||
|
return toast.error('Error tijdens het registreren van push notifications')
|
||||||
|
}
|
||||||
|
|
||||||
|
userStore.registrationToken = currentToken
|
||||||
|
console.log('Subscribed to topic!')
|
||||||
|
} else {
|
||||||
|
// Show permission request UI
|
||||||
|
console.log('No registration token available. Request permission to generate one.');
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log('An error occurred while retrieving token. ', err);
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setupNotifications = async () => {
|
||||||
|
const info = await Device.getInfo();
|
||||||
|
|
||||||
|
if (info.platform !== 'web') setupIosNotifications()
|
||||||
|
else setupWebNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerServiceWorker = () => {
|
||||||
|
const messaging = getMessaging()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
navigator.serviceWorker.register('/sw.js').catch(e => alert(e));
|
||||||
|
|
||||||
|
onMessage(messaging, (payload) => {
|
||||||
|
console.log('Message received. ', payload);
|
||||||
|
|
||||||
|
toast.info(`${payload.notification.title}`, {
|
||||||
|
onClick: () => navigateTo('/news')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
6
frontend/composables/useToast.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const toast = useToast()
|
||||||
|
return toast
|
||||||
|
}
|
13
frontend/ios/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
App/build
|
||||||
|
App/Pods
|
||||||
|
App/Podfile.lock
|
||||||
|
App/App/public
|
||||||
|
DerivedData
|
||||||
|
xcuserdata
|
||||||
|
|
||||||
|
# Cordova plugins for Capacitor
|
||||||
|
capacitor-cordova-ios-plugins
|
||||||
|
|
||||||
|
# Generated Config files
|
||||||
|
App/App/capacitor.config.json
|
||||||
|
App/App/config.xml
|
406
frontend/ios/App/App.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 48;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
|
||||||
|
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
|
||||||
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
|
||||||
|
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
|
||||||
|
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
|
||||||
|
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
||||||
|
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
||||||
|
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
|
||||||
|
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||||
|
504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
|
||||||
|
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
504EC3011FED79650016851F /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
504EC2FB1FED79650016851F = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
504EC3061FED79650016851F /* App */,
|
||||||
|
504EC3051FED79650016851F /* Products */,
|
||||||
|
7F8756D8B27F46E3366F6CEA /* Pods */,
|
||||||
|
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
504EC3051FED79650016851F /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
504EC3041FED79650016851F /* App.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
504EC3061FED79650016851F /* App */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
50379B222058CBB4000EE86E /* capacitor.config.json */,
|
||||||
|
504EC3071FED79650016851F /* AppDelegate.swift */,
|
||||||
|
504EC30B1FED79650016851F /* Main.storyboard */,
|
||||||
|
504EC30E1FED79650016851F /* Assets.xcassets */,
|
||||||
|
504EC3101FED79650016851F /* LaunchScreen.storyboard */,
|
||||||
|
504EC3131FED79650016851F /* Info.plist */,
|
||||||
|
2FAD9762203C412B000D30F8 /* config.xml */,
|
||||||
|
50B271D01FEDC1A000F3C39B /* public */,
|
||||||
|
);
|
||||||
|
path = App;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7F8756D8B27F46E3366F6CEA /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
|
||||||
|
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
504EC3031FED79650016851F /* App */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
|
||||||
|
buildPhases = (
|
||||||
|
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
504EC3001FED79650016851F /* Sources */,
|
||||||
|
504EC3011FED79650016851F /* Frameworks */,
|
||||||
|
504EC3021FED79650016851F /* Resources */,
|
||||||
|
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = App;
|
||||||
|
productName = App;
|
||||||
|
productReference = 504EC3041FED79650016851F /* App.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
504EC2FC1FED79650016851F /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastSwiftUpdateCheck = 0920;
|
||||||
|
LastUpgradeCheck = 0920;
|
||||||
|
TargetAttributes = {
|
||||||
|
504EC3031FED79650016851F = {
|
||||||
|
CreatedOnToolsVersion = 9.2;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
ProvisioningStyle = Automatic;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */;
|
||||||
|
compatibilityVersion = "Xcode 8.0";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 504EC2FB1FED79650016851F;
|
||||||
|
productRefGroup = 504EC3051FED79650016851F /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
504EC3031FED79650016851F /* App */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
504EC3021FED79650016851F /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
|
||||||
|
50B271D11FEDC1A000F3C39B /* public in Resources */,
|
||||||
|
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
|
||||||
|
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
|
||||||
|
504EC30D1FED79650016851F /* Main.storyboard in Resources */,
|
||||||
|
2FAD9763203C412B000D30F8 /* config.xml in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
504EC3001FED79650016851F /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
504EC30B1FED79650016851F /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
504EC30C1FED79650016851F /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
504EC3111FED79650016851F /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
504EC3141FED79650016851F /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
504EC3151FED79650016851F /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
504EC3171FED79650016851F /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.reddingsbrigadewaddinxveen.wrbapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
504EC3181FED79650016851F /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.reddingsbrigadewaddinxveen.wrbapp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
504EC3141FED79650016851F /* Debug */,
|
||||||
|
504EC3151FED79650016851F /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
504EC3171FED79650016851F /* Debug */,
|
||||||
|
504EC3181FED79650016851F /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 504EC2FC1FED79650016851F /* Project object */;
|
||||||
|
}
|
7
frontend/ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:App.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
10
frontend/ios/App/App.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:App.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
66
frontend/ios/App/App/AppDelegate.swift
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import UIKit
|
||||||
|
import Capacitor
|
||||||
|
import Firebase
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
// Override point for customization after application launch.
|
||||||
|
FirebaseApp.configure()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationWillResignActive(_ application: UIApplication) {
|
||||||
|
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||||
|
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||||
|
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||||
|
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||||
|
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
|
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||||
|
}
|
||||||
|
|
||||||
|
func applicationWillTerminate(_ application: UIApplication) {
|
||||||
|
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||||
|
// Called when the app was launched with a url. Feel free to add additional processing here,
|
||||||
|
// but if you want the App API to support tracking app url opens, make sure to keep this call
|
||||||
|
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||||
|
// Called when the app was launched with an activity, including Universal Links.
|
||||||
|
// Feel free to add additional processing here, but if you want the App API to support
|
||||||
|
// tracking app url opens, make sure to keep this call
|
||||||
|
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
|
Messaging.messaging().apnsToken = deviceToken
|
||||||
|
Messaging.messaging().token(completion: { (token, error) in
|
||||||
|
if let error = error {
|
||||||
|
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
|
||||||
|
} else if let token = token {
|
||||||
|
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: token)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||||
|
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 739 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,116 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-29x29@2x-1.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "AppIcon-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-20x20@2x-1.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-40x40@2x-1.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "AppIcon-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "AppIcon-512@2x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
6
frontend/ios/App/App/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
23
frontend/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "splash-2732x2732-2.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "splash-2732x2732-1.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "splash-2732x2732.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png
vendored
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png
vendored
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
frontend/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png
vendored
Normal file
After Width: | Height: | Size: 131 KiB |
32
frontend/ios/App/App/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
|
||||||
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
</imageView>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="Splash" width="1366" height="1366"/>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
19
frontend/ios/App/App/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<device id="retina4_7" orientation="portrait">
|
||||||
|
<adaptation id="fullscreen"/>
|
||||||
|
</device>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Bridge View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
34
frontend/ios/App/App/GoogleService-Info.plist
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CLIENT_ID</key>
|
||||||
|
<string>160377508482-r1822jlgp287ks09ajb27qg1oko80lpr.apps.googleusercontent.com</string>
|
||||||
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
|
<string>com.googleusercontent.apps.160377508482-r1822jlgp287ks09ajb27qg1oko80lpr</string>
|
||||||
|
<key>API_KEY</key>
|
||||||
|
<string>AIzaSyAjlJhSGUMzTFVx-ICML_8DVlDFUQqN8WY</string>
|
||||||
|
<key>GCM_SENDER_ID</key>
|
||||||
|
<string>160377508482</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>com.reddingsbrigadewaddinxveen.wrbapp</string>
|
||||||
|
<key>PROJECT_ID</key>
|
||||||
|
<string>wrbapp</string>
|
||||||
|
<key>STORAGE_BUCKET</key>
|
||||||
|
<string>wrbapp.appspot.com</string>
|
||||||
|
<key>IS_ADS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_ANALYTICS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_APPINVITE_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_GCM_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_SIGNIN_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>GOOGLE_APP_ID</key>
|
||||||
|
<string>1:160377508482:ios:0079517b62e9684f879a9b</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
49
frontend/ios/App/App/Info.plist
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>wrbapp</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(MARKETING_VERSION)</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>armv7</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
26
frontend/ios/App/Podfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
|
||||||
|
|
||||||
|
platform :ios, '13.0'
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
# workaround to avoid Xcode caching of Pods that requires
|
||||||
|
# Product -> Clean Build Folder after new Cordova plugins installed
|
||||||
|
# Requires CocoaPods 1.6 or newer
|
||||||
|
install! 'cocoapods', :disable_input_output_paths => true
|
||||||
|
|
||||||
|
def capacitor_pods
|
||||||
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
|
pod 'CapacitorCommunityFcm', :path => '../../node_modules/@capacitor-community/fcm'
|
||||||
|
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'
|
||||||
|
pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications'
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'App' do
|
||||||
|
capacitor_pods
|
||||||
|
pod 'Firebase/Messaging'
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
assertDeploymentTarget(installer)
|
||||||
|
end
|
5
frontend/jsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
// https://v3.nuxtjs.org/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
|
"allowJs": true
|
||||||
|
}
|
41
frontend/nuxt.config.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
ssr: false,
|
||||||
|
imports: {
|
||||||
|
dirs: ['stores']
|
||||||
|
},
|
||||||
|
modules: [
|
||||||
|
'@nuxtjs/tailwindcss',
|
||||||
|
'nuxt-icon',
|
||||||
|
'@vueuse/nuxt',
|
||||||
|
'@nuxtjs/robots',
|
||||||
|
[ '@pinia/nuxt', { autoImports: [ 'defineStore' ]} ],
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
transpile: ['vue-toastification'],
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
head: {
|
||||||
|
title: 'WRB App',
|
||||||
|
charset: 'utf-8',
|
||||||
|
viewport: 'width=device-width initial-scale=1 viewport-fit=cover',
|
||||||
|
meta: [
|
||||||
|
{ name: 'theme-color', content: '#eb6330' },
|
||||||
|
{ name: 'description', content: 'De officiele app voor de Waddinxveense Reddingsbrigade'}
|
||||||
|
],
|
||||||
|
link: [
|
||||||
|
{ rel: 'manifest', href: '/manifest.json' },
|
||||||
|
{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }
|
||||||
|
],
|
||||||
|
script: [
|
||||||
|
{ src: 'https://umami.xeovalyte.dev/script.js', async: true, 'data-website-id': '59577dd0-b790-488c-af69-7f8d2cce0537' },
|
||||||
|
],
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
runtimeConfig: {
|
||||||
|
privateKeyId: '',
|
||||||
|
privateKey: '',
|
||||||
|
clientId: ''
|
||||||
|
}
|
||||||
|
})
|
22038
frontend/package-lock.json
generated
Normal file
38
frontend/package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"overrides": {
|
||||||
|
"vue": "latest"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@capacitor/cli": "^4.7.1",
|
||||||
|
"@nuxtjs/tailwindcss": "^6.6.0",
|
||||||
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
"@vueuse/core": "^9.13.0",
|
||||||
|
"@vueuse/nuxt": "^9.13.0",
|
||||||
|
"nuxt": "^3.3.1",
|
||||||
|
"nuxt-icon": "^0.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@capacitor/core": "^4.7.1",
|
||||||
|
"@capacitor/device": "^4.1.0",
|
||||||
|
"@capacitor/ios": "^4.7.1",
|
||||||
|
"@capacitor/push-notifications": "^4.1.2",
|
||||||
|
"@formkit/nuxt": "^1.0.0-beta.11-c95e605",
|
||||||
|
"@nuxtjs/robots": "^3.0.0",
|
||||||
|
"@pinia/nuxt": "^0.4.7",
|
||||||
|
"@vueuse/components": "^9.13.0",
|
||||||
|
"@vueuse/firebase": "^9.13.0",
|
||||||
|
"@vueuse/shared": "^9.13.0",
|
||||||
|
"firebase": "^9.18.0",
|
||||||
|
"firebase-admin": "^11.5.0",
|
||||||
|
"pinia": "^2.0.33",
|
||||||
|
"vue-toastification": "^2.0.0-rc.5"
|
||||||
|
}
|
||||||
|
}
|
55
frontend/pages/calendar.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<div v-if="calendarStore.events[0]" class="flex flex-col gap-3">
|
||||||
|
<div v-for="(event, index) in calendarStore.events" :key="index">
|
||||||
|
<div class="item container flex flex-col">
|
||||||
|
<h2 class="">{{ longEventDate(event.date) }}</h2>
|
||||||
|
<p class="whitespace-pre overflow-x-auto font-bold text-xl">{{ event.description}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col justify-center items-center" v-else>
|
||||||
|
<Icon size="2em" name="ion:load-c" class="animate-spin" />
|
||||||
|
<h2 class="mt-2 font-bold">Loading...</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Agenda'
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const calendarStore = useCalendarStore()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getEvents()
|
||||||
|
})
|
||||||
|
|
||||||
|
const getEvents = () => {
|
||||||
|
|
||||||
|
if (userStore.userPersons[0]) {
|
||||||
|
const groups = [...new Set(userStore.userPersons.map(a => a.groups.join()).join().split(','))]
|
||||||
|
|
||||||
|
calendarStore.getEvents(groups)
|
||||||
|
} else {
|
||||||
|
setTimeout(() => { getEvents() }, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const longEventDate = (eventDate) => {
|
||||||
|
const date = new Date(eventDate)
|
||||||
|
|
||||||
|
return date.toLocaleString('nl-NL', {
|
||||||
|
weekday: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
29
frontend/pages/index.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col justify-center items-center px-2 overflow-hidden">
|
||||||
|
<h1 class="font-bold text-3xl text-center mt-6 mb-3">Reddingsbrigade Waddinxveen</h1>
|
||||||
|
<h2 class="text-xl text-center mb-12">{{ userStore.userPersons.map(a => a.fullName).join(', ')}} {{ userStore.userPerons }}</h2>
|
||||||
|
<div class="container w-full max-w-md">
|
||||||
|
<NuxtLink to="/news" class="rounded-t item-hover py-2 flex items-center">
|
||||||
|
<span>Nieuws</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="divider" />
|
||||||
|
<NuxtLink to="/calendar" class="item-hover py-2 flex items-center">
|
||||||
|
<span>Agenda</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="divider" />
|
||||||
|
<NuxtLink to="/settings" class="rounded-b item-hover py-2 flex items-center">
|
||||||
|
<span>Settings</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Home',
|
||||||
|
})
|
||||||
|
const userStore = useUserStore()
|
||||||
|
</script>
|
86
frontend/pages/news/index.vue
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<div v-if="newsStore.loaded && newsStore.news" class="flex flex-col gap-3">
|
||||||
|
<NuxtLink to="/news/newmessage" v-if="userStore.userData.sendNews" class="item-hover border-dashed border-2 container text-center font-bold text-xl border-neutral-500 mb-3">
|
||||||
|
Nieuw Bericht
|
||||||
|
</NuxtLink>
|
||||||
|
<div v-if="newsStore.news[0]" v-for="(item, index) in newsStore.news" :key="index">
|
||||||
|
<div class="item container flex flex-col relative">
|
||||||
|
<h3 class="text-sm">{{ longEventDate(item.date.toDate()) }}</h3>
|
||||||
|
<h2 class="text-2xl font-bold">{{ item.title }}</h2>
|
||||||
|
<p class="description" v-html="convertLinks(item.description)"></p>
|
||||||
|
<Icon v-if="userStore.userData.sendNews" @click="newsStore.deleteNews(item, index)" size="1.5em" name="ion:trash-sharp" class="absolute top-3 right-3 hover:cursor-pointer text-red-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2 v-else class="font-bold text-center text-xl mt-3">
|
||||||
|
Er is geen nieuws
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col justify-center items-center" v-else>
|
||||||
|
<Icon size="2em" name="ion:load-c" class="animate-spin" />
|
||||||
|
<h2 class="mt-2 font-bold">Loading...</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Nieuws'
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const newsStore = useNewsStore()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
newsStore.getNews()
|
||||||
|
})
|
||||||
|
|
||||||
|
const convertLinks = ( input ) => {
|
||||||
|
|
||||||
|
let text = input;
|
||||||
|
const linksFound = text.match( /(?:www|https?)[^\s]+/g );
|
||||||
|
const aLink = [];
|
||||||
|
|
||||||
|
if ( linksFound != null ) {
|
||||||
|
|
||||||
|
for ( let i=0; i<linksFound.length; i++ ) {
|
||||||
|
let replace = linksFound[i];
|
||||||
|
if ( !( linksFound[i].match( /(http(s?)):\/\// ) ) ) { replace = 'http://' + linksFound[i] }
|
||||||
|
let linkText = replace.split( '/' )[2];
|
||||||
|
if ( linkText.substring( 0, 3 ) == 'www' ) { linkText = linkText.replace( 'www.', '' ) }
|
||||||
|
if ( linkText.match( /youtu/ ) ) {
|
||||||
|
|
||||||
|
let youtubeID = replace.split( '/' ).slice(-1)[0];
|
||||||
|
aLink.push( '<div class="video-wrapper"><iframe src="https://www.youtube.com/embed/' + youtubeID + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>' )
|
||||||
|
}
|
||||||
|
else if ( linkText.match( /vimeo/ ) ) {
|
||||||
|
let vimeoID = replace.split( '/' ).slice(-1)[0];
|
||||||
|
aLink.push( '<div class="video-wrapper"><iframe src="https://player.vimeo.com/video/' + vimeoID + '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>' )
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
aLink.push( '<a class="text-primary hover:underline underline-offset-2" href="' + replace + '" target="_blank">' + linkText + '</a>' );
|
||||||
|
}
|
||||||
|
text = text.split( linksFound[i] ).map(item => { return aLink[i].includes('iframe') ? item.trim() : item } ).join( aLink[i] );
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const longEventDate = (eventDate) => {
|
||||||
|
const date = new Date(eventDate)
|
||||||
|
|
||||||
|
return date.toLocaleString('nl-NL', {
|
||||||
|
weekday: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
</script>
|
56
frontend/pages/news/newmessage.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<form @submit.prevent="sendNews" class="flex flex-col">
|
||||||
|
<label class="font-bold">Titel</label>
|
||||||
|
<input v-model="form.title" required="true" class="input mb-5" type="text">
|
||||||
|
|
||||||
|
<label class="font-bold">Beschrijving</label>
|
||||||
|
<textarea v-model="form.description" required="true" class="input mb-5" />
|
||||||
|
|
||||||
|
<label class="font-bold">Groep</label>
|
||||||
|
<select v-model="form.topic" required="true" class="input mb-5">
|
||||||
|
<option value="all">Iedereen</option>
|
||||||
|
<option value="test">Test</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-wrap justify-between">
|
||||||
|
<input :disabled="disableButtons" type="submit" value="Stuur Bericht" class="btn w-full sm:w-40 mb-1">
|
||||||
|
<button @click="router.back()" type="button" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Annuleer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Nieuw Bericht',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
const newsStore = useNewsStore()
|
||||||
|
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
|
||||||
|
const sendNews = async () => {
|
||||||
|
try {
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
await newsStore.send(form.value)
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
disableButtons.value = false
|
||||||
|
|
||||||
|
toast.error('Error tijdens versturen bericht')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
topic: ''
|
||||||
|
})
|
||||||
|
</script>
|
265
frontend/pages/settings/admin/ledenlijst.vue
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModelForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Beheer Persoon</h1>
|
||||||
|
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Relatiecode: <b>{{ modelData.relatiecode }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Naam: <b>{{ modelData.fullName }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-5">
|
||||||
|
Groepen: <b>{{ modelData.groups.join(', ') }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.wedstrijdteam" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Wedstrijd Team</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn" :value="'Bewerken'" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<div class="mb-5">
|
||||||
|
<form @submit.prevent="submitLedenlijst" class="flex flex-col">
|
||||||
|
<input required="true" @change="handleFileChanged" accept=".csv" class="my-2" type="file">
|
||||||
|
<span class="text-sm"><i>Met de volgende kolommen: Relatiecode, Volledige naam(1), Roepnaam, E-mail, 2e E-mail, Verenigingssporten, Diploma</i></span>
|
||||||
|
<button :disabled="disableButtons" class="btn mx-auto mt-2">Publish Ledenlijst</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<input v-model="searchTerm" class="input mb-2 font-bold" type="search" placeholder="Search">
|
||||||
|
<div v-for="lid in filteredLedenlijst" :key="lid.relatiecode">
|
||||||
|
<div @click="handleModel(lid)" class="item container flex flex-wrap hover:cursor-pointer">
|
||||||
|
<b class="w-24">{{ lid.relatiecode }}</b> {{ lid.fullName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { doc, getDocs, collection, writeBatch, updateDoc, setDoc, getFirestore } from "firebase/firestore";
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Ledenlijst',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const usersStore = useUsersStore()
|
||||||
|
const modelData = ref(null)
|
||||||
|
const db = getFirestore()
|
||||||
|
|
||||||
|
const file = ref(null)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
const searchTerm = ref('')
|
||||||
|
const newLedenlijst = ref([])
|
||||||
|
const showModel = ref(false)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
usersStore.getLedenlijst()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleModel = (lid) => {
|
||||||
|
modelData.value = lid
|
||||||
|
if (!modelData.value.hasOwnProperty('wedstrijdteam')) modelData.value.wedstrijdteam = false
|
||||||
|
|
||||||
|
modelData.value.oldWedstrijdteam = modelData.value.wedstrijdteam
|
||||||
|
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModelForm = async () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
usersStore.ledenlijst.filter(a => a.relatiecode === modelData.value.relatiecode)[0].wedstrijdteam = modelData.value.wedstrijdteam
|
||||||
|
|
||||||
|
if (modelData.value.wedstrijdteam === modelData.value.oldWedstrijdteam) {
|
||||||
|
disableButtons.value = false
|
||||||
|
showModel.value = false
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let docRef = doc(db, "ledenlijst", modelData.value.relatiecode)
|
||||||
|
|
||||||
|
await updateDoc(docRef, {
|
||||||
|
wedstrijdteam: modelData.value.wedstrijdteam
|
||||||
|
})
|
||||||
|
|
||||||
|
docRef = doc(db, "competitors", modelData.value.relatiecode)
|
||||||
|
|
||||||
|
if (modelData.value.wedstrijdteam) {
|
||||||
|
await setDoc(docRef, {
|
||||||
|
relatiecode: modelData.value.relatiecode,
|
||||||
|
name: modelData.value.fullName,
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await updateDoc(docRef, {
|
||||||
|
active: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Persoon is bewerkt')
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
showModel.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredLedenlijst = computed(() => {
|
||||||
|
return usersStore.ledenlijst.filter(lid => lid.fullName.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleFileChanged = (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
|
||||||
|
if (target && target.files) {
|
||||||
|
file.value = target.files[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitLedenlijst = () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
let reader = new FileReader()
|
||||||
|
|
||||||
|
reader.onload = function() {
|
||||||
|
csvToJson(reader.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = function() {
|
||||||
|
console.log(reader.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const csvToJson = (csv) => {
|
||||||
|
let arr = csv.split('\n');
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
var headers = arr[0].split(';');
|
||||||
|
for(var i = 1; i < arr.length; i++) {
|
||||||
|
var data = arr[i].split(';');
|
||||||
|
var obj = {};
|
||||||
|
for(var j = 0; j < data.length; j++) {
|
||||||
|
obj[headers[j].trim()] = data[j].trim();
|
||||||
|
}
|
||||||
|
result.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.hasOwn(result[0], 'Relatiecode') || !Object.hasOwn(result[0], 'Volledige naam (1)') || !Object.hasOwn(result[0], 'E-mail') || !Object.hasOwn(result[0], '2e E-mail') || !Object.hasOwn(result[0], 'Verenigingssporten') || !Object.hasOwn(result[0], 'Diploma dropdown 1')) return toast.error('Missing properties')
|
||||||
|
|
||||||
|
newLedenlijst.value = []
|
||||||
|
|
||||||
|
for (let i in result) {
|
||||||
|
let groups = []
|
||||||
|
let correctGroups = null
|
||||||
|
if (!result[i].Relatiecode) break;
|
||||||
|
|
||||||
|
if (result[i].Verenigingssporten.includes(',')) correctGroups = result[i].Verenigingssporten.split(',')
|
||||||
|
else correctGroups = [result[i].Verenigingssporten]
|
||||||
|
|
||||||
|
correctGroups.forEach(group => {
|
||||||
|
const x = group.split(' - ')
|
||||||
|
|
||||||
|
if (x[2] === 'Week') groups.push('Vrijdag')
|
||||||
|
else if (x[2] === 'Zaterdag' && x[1] !== 'Wedstrijd') groups.push('Zaterdag')
|
||||||
|
|
||||||
|
groups.push(x[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
if (groups[2] === 'Week') groups[2] = 'Vrijdag'
|
||||||
|
|
||||||
|
let inwedstrijdteam
|
||||||
|
if (usersStore.ledenlijst.filter(x => x.relatiecode === result[i].Relatiecode)[0]) {
|
||||||
|
inwedstrijdteam = usersStore.ledenlijst.filter(x => x.relatiecode === result[i].Relatiecode)[0].wedstrijdteam;
|
||||||
|
} else {
|
||||||
|
inwedstrijdteam = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wedstrijdteam = inwedstrijdteam ? true : false
|
||||||
|
|
||||||
|
newLedenlijst.value.push({ relatiecode: result[i].Relatiecode, wedstrijdteam, fullName: result[i]['Volledige naam (1)'], email: [result[i]['E-mail'], result[i]['2e E-mail']], groups: [...new Set(groups)], diploma: result[i]['Diploma dropdown 1'] })
|
||||||
|
}
|
||||||
|
uploadLedenlijst()
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadLedenlijst = async () => {
|
||||||
|
try {
|
||||||
|
const batch = writeBatch(db)
|
||||||
|
|
||||||
|
newLedenlijst.value.forEach(lid => {
|
||||||
|
const docRef = doc(db, "ledenlijst", lid.relatiecode)
|
||||||
|
|
||||||
|
const exists = usersStore.ledenlijst.filter(a => a.relatiecode === lid.relatiecode).length > 1
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
return batch.set(docRef, lid);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.update(docRef, { fullName: lid.relatiecode, email: lid.email, groups: lid.groups, diploma: lid.diploma})
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteLeden = usersStore.ledenlijst.filter(a => newLedenlijst.value.map(x => x.relatiecode).indexOf(a.relatiecode) === -1)
|
||||||
|
|
||||||
|
deleteLeden.forEach(lid => {
|
||||||
|
const docRef = doc(db, "ledenlijst", lid.relatiecode)
|
||||||
|
|
||||||
|
batch.delete(docRef)
|
||||||
|
})
|
||||||
|
|
||||||
|
await batch.commit();
|
||||||
|
|
||||||
|
toast.success('Published ledenlijst')
|
||||||
|
} catch (e) {
|
||||||
|
toast.error("Error updating ledenlijst");
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
usersStore.ledenlijst = newLedenlijst.value
|
||||||
|
|
||||||
|
updateUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const updateUsers = async () => {
|
||||||
|
try {
|
||||||
|
const querySnapshot = await getDocs(collection(db, "users"));
|
||||||
|
querySnapshot.forEach((doc) => {
|
||||||
|
const data = doc.data()
|
||||||
|
data.id = doc.id
|
||||||
|
usersStore.users.push(data)
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
|
||||||
|
toast.error('Error getting users')
|
||||||
|
}
|
||||||
|
|
||||||
|
const batch = writeBatch(db);
|
||||||
|
|
||||||
|
usersStore.users.forEach(user => {
|
||||||
|
const lid = usersStore.ledenlijst.filter(a => a.email.includes(user.email))
|
||||||
|
const newRelatiecodes = lid.map(a => a.relatiecode)
|
||||||
|
|
||||||
|
user.allRelatiecodes = newRelatiecodes
|
||||||
|
user.relatiecodes.forEach((relatiecode, index) => {
|
||||||
|
if (!newRelatiecodes.includes(relatiecode)) { user.relatiecodes.splice(index, 1); console.log('removed item', relatiecode)}
|
||||||
|
})
|
||||||
|
|
||||||
|
const userRef = doc(db, "users", user.id)
|
||||||
|
batch.update(userRef, user)
|
||||||
|
})
|
||||||
|
|
||||||
|
await batch.commit();
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
}
|
||||||
|
</script>
|
98
frontend/pages/settings/admin/users.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModelForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Beheer Gebruiker</h1>
|
||||||
|
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Relatiecodes: <b>{{ modelData.relatiecodes.join(', ') }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-2">
|
||||||
|
Email: <b>{{ modelData.email }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-default mb-5">
|
||||||
|
ID: <b>{{ modelData.id }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.admin" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Admin</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.sendNews" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Berichten Sturen</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="relative inline-flex items-center cursor-pointer mb-5">
|
||||||
|
<input type="checkbox" value="" v-model="modelData.wedstrijdAdmin" class="sr-only peer">
|
||||||
|
<div class="w-11 h-6 bg-neutral-300 peer-focus:outline-none peer-focus:ring-none rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary"></div>
|
||||||
|
<span class="ml-3 font-medium text-gray-900 dark:text-gray-300">Wedstrijd Admin</span>
|
||||||
|
</label>
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn" :value="'Bewerken'" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<input v-model="searchTerm" class="input mb-2 font-bold" type="search" placeholder="Search">
|
||||||
|
<div v-for="user in filteredUsers" :key="user.relatiecode">
|
||||||
|
<div @click="handleModel(user)" class="item container flex flex-wrap hover:cursor-pointer">
|
||||||
|
<b class="w-24">{{ user.email }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getDocs, collection, doc, updateDoc, getFirestore } from 'firebase/firestore'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Manage users',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const db = getFirestore()
|
||||||
|
const usersStore = useUsersStore()
|
||||||
|
|
||||||
|
const searchTerm = ref('')
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
const showModel = ref(false)
|
||||||
|
|
||||||
|
const modelData = ref({})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
usersStore.getUsers()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleModel = (user) => {
|
||||||
|
modelData.value = user
|
||||||
|
if (!modelData.value.hasOwnProperty('admin')) modelData.value.admin = false
|
||||||
|
if (!modelData.value.hasOwnProperty('sendNews')) modelData.value.sendNews = false
|
||||||
|
if (!modelData.value.hasOwnProperty('wedstrijdAdmin')) modelData.value.wedstrijdAdmin = false
|
||||||
|
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredUsers = computed(() => {
|
||||||
|
return usersStore.users.filter(user => user.email.toLowerCase().includes(searchTerm.value.toLowerCase()))
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitModelForm = async () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
const docRef = doc(db, "users", modelData.value.id)
|
||||||
|
|
||||||
|
await updateDoc(docRef, {
|
||||||
|
admin: modelData.value.admin,
|
||||||
|
sendNews: modelData.value.sendNews,
|
||||||
|
wedstrijdAdmin: modelData.value.wedstrijdAdmin
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.success('Gebruiker is bewerkt')
|
||||||
|
|
||||||
|
disableButtons.value = false
|
||||||
|
showModel.value = false
|
||||||
|
}
|
||||||
|
</script>
|
87
frontend/pages/settings/config/changepassword.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<form @submit.prevent="savePassword" class="flex flex-col">
|
||||||
|
<label class="font-bold">Oud Wachtwoord</label>
|
||||||
|
<input v-model="form.oldPassword" required="true" class="input mb-5" :type="showPassword ? 'text' : 'password'">
|
||||||
|
|
||||||
|
<label class="font-bold">Nieuw Wachtwoord</label>
|
||||||
|
<input v-model="form.newPassword" required="true" class="input mb-5" :type="showPassword ? 'text' : 'password'">
|
||||||
|
|
||||||
|
<label class="font-bold">Herhaal Nieuw Wachtwoord</label>
|
||||||
|
<input v-model="form.confirmNewPassword" required="true" class="input" :type="showPassword ? 'text' : 'password'">
|
||||||
|
|
||||||
|
<div class="mb-5 mt-1 flex items-center text-default">
|
||||||
|
<input v-model="showPassword" type="checkbox" class="mr-1 checkbox ">
|
||||||
|
<span>Toon Wachtwoord</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-wrap justify-between">
|
||||||
|
<input :disabled="disableButtons" type="submit" value="Wijzig Wachtwoord" class="btn w-full sm:w-52 mb-1">
|
||||||
|
<button @click="router.back()" class="hover:underline font-bold w-full sm:w-max sm:ml-auto">Annuleer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reauthenticateWithCredential, EmailAuthProvider, updatePassword } from 'firebase/auth'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Wachtwoord Wijzigen',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const form = ref({
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmNewPassword: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
form.value = {
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmNewPassword: '',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const savePassword = () => {
|
||||||
|
if (form.value.newPassword !== form.value.confirmNewPassword) return alert ('Niewe wachtwoorden zijn niet hetzelfde')
|
||||||
|
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
const credential = EmailAuthProvider.credential(
|
||||||
|
userStore.user.email,
|
||||||
|
form.value.oldPassword
|
||||||
|
)
|
||||||
|
|
||||||
|
reauthenticateWithCredential(userStore.auth.currentUser, credential).then(() => {
|
||||||
|
updatePassword(userStore.auth.currentUser, form.value.newPassword).then(() => {
|
||||||
|
toast.success('Wachtwoord is veranderd')
|
||||||
|
|
||||||
|
navigateTo('/settings')
|
||||||
|
disableButtons.value = false
|
||||||
|
}).catch((error) => {
|
||||||
|
toast.error('Error tijdens het wachtwoord veranderen')
|
||||||
|
console.log(error)
|
||||||
|
disableButtons.value = false
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
disableButtons.value = false
|
||||||
|
|
||||||
|
if (error.code === 'auth/wrong-password') return toast.error('Oude wachtwoord is onjuist')
|
||||||
|
|
||||||
|
toast.error('Error tijdens het wachtwoord veranderen')
|
||||||
|
|
||||||
|
console.log(error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const showPassword = ref(false)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
</script>
|
69
frontend/pages/settings/config/managerelatiecodes.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<div v-if="userStore.userAllPersons.length !== 0 && userStore.userPersons.length !== 0" class="flex flex-col gap-3">
|
||||||
|
<div v-for="person in userStore.userAllPersons" :key="person.relatiecode">
|
||||||
|
<div @click="updateCheckbox(person)" class="item container flex flex-wrap" :class="person.relatiecode === userStore.userPersons[0].relatiecode ? 'bg-neutral-200 dark:bg-neutral-850 text-neutral-400 dark:text-neutral-500 hover:cursor-not-allowed' : 'hover:cursor-pointer'">
|
||||||
|
<input v-model="person.checked" :disabled="person.relatiecode === userStore.userPersons[0].relatiecode" class="checkbox my-auto mr-3 disabled:bg-neutral-300 disabled:hover:text-neutral-300 dark:disabled:bg-neutral-600 dark:disabled:hover:text-neutral-600 disabled:hover:cursor-not-allowed" type="checkbox">
|
||||||
|
<span><b>{{ person.fullName }}</b></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-wrap">
|
||||||
|
<button :disabled="buttonsDisabled" @click="save" class="btn w-full sm:w-40 mb-1">Opslaan</button>
|
||||||
|
<span @click="router.back()" class="hover:underline font-bold w-full text-center sm:w-max sm:ml-auto hover:cursor-pointer">Annuleer</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col justify-center items-center" v-else>
|
||||||
|
<Icon size="2em" name="ion:load-c" class="animate-spin" />
|
||||||
|
<h2 class="mt-2 font-bold">Loading...</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { updateDoc, doc, getFirestore } from 'firebase/firestore'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Beheer Personen',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const db = getFirestore()
|
||||||
|
|
||||||
|
const buttonsDisabled = ref(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
userStore.getAllPersons()
|
||||||
|
})
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
buttonsDisabled.value = true
|
||||||
|
|
||||||
|
const newRelatiecodes = []
|
||||||
|
|
||||||
|
userStore.userAllPersons.forEach(person => {
|
||||||
|
if (person.checked) {
|
||||||
|
newRelatiecodes.push(person.relatiecode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await updateDoc(doc(db, "users", userStore.user.uid), {
|
||||||
|
relatiecodes: newRelatiecodes
|
||||||
|
})
|
||||||
|
|
||||||
|
userStore.getPersons(newRelatiecodes)
|
||||||
|
|
||||||
|
buttonsDisabled.value = false
|
||||||
|
navigateTo('/settings')
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCheckbox = (person) => {
|
||||||
|
if (person.relatiecode === userStore.userPersons[0].relatiecode) return;
|
||||||
|
|
||||||
|
person.checked = !person.checked
|
||||||
|
}
|
||||||
|
</script>
|
85
frontend/pages/settings/index.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl ml-2 font-bold">Info</h1>
|
||||||
|
<div class="container">
|
||||||
|
<div class="item">
|
||||||
|
Email: <b>{{ userStore.user.email }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="divider" />
|
||||||
|
<div class="item">
|
||||||
|
Personen: <b>{{ userStore.userPersons.map(a => a.fullName).join(', ')}}</b>
|
||||||
|
</div>
|
||||||
|
<div class="divider" />
|
||||||
|
<div class="item">
|
||||||
|
Groepen: <b>{{ groups.join(', ') }}</b>
|
||||||
|
</div>
|
||||||
|
<div v-if="userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="divider" />
|
||||||
|
<div v-if="userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join('')" class="item">
|
||||||
|
Diploma: <b>{{ userStore.userPersons.map(a => a.diploma).filter(n => n !== '').join(', ')}}</b>
|
||||||
|
</div>
|
||||||
|
<div class="divider" />
|
||||||
|
<NuxtLink to="/settings/moreinfo" class="item-hover py-2 rounded-t flex items-center">
|
||||||
|
<span>Meer Informatie</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl ml-2 font-bold">Account</h1>
|
||||||
|
<div class="container">
|
||||||
|
<NuxtLink to="/settings/config/managerelatiecodes" class="item-hover py-2 rounded-t flex items-center">
|
||||||
|
<span>Beheer Personen</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="divider" />
|
||||||
|
<NuxtLink to="/settings/config/changepassword" class="item-hover py-2 flex items-center">
|
||||||
|
<span>Wachtwoord Wijzigen</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="divider" />
|
||||||
|
<div @click="logout" class="item-hover rounded-b flex items-center">
|
||||||
|
Uitloggen
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="userStore.userData.admin">
|
||||||
|
<h1 class="text-xl ml-2 font-bold">Admin</h1>
|
||||||
|
<div class="container">
|
||||||
|
<NuxtLink to="/settings/admin/users" class="rounded-t item-hover py-2 flex items-center">
|
||||||
|
<span>Beheer gebruikers</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="divider" />
|
||||||
|
<NuxtLink to="/settings/admin/ledenlijst" class="rounded-b item-hover py-2 flex items-center">
|
||||||
|
<span>Ledenlijst</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-center font-bold">Gemaakt door <u><a href="https://xeovalyte.com/">Timo Boomers</a></u></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { signOut } from "firebase/auth";
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Settings'
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const groups = computed(() => {
|
||||||
|
return [...new Set(userStore.userPersons.map(a => a.groups.join()).join().split(','))]
|
||||||
|
})
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
signOut(userStore.auth)
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
36
frontend/pages/settings/moreinfo.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md text-default text-sm">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl ml-2 font-bold">Privacybeleid</h1>
|
||||||
|
<div class="container">
|
||||||
|
<div class="item">
|
||||||
|
<h2 class="font-bold">Privacy</h2>
|
||||||
|
Gegevens binnen deze app worden gebruikt voor de interne organisatie. Via Google analytics wordt bijgehouden welke schermen het meest worden gebruikt. Daarnaast maken wij gebruik van Firebase voor het anoniem verzamelen van gegevens omtrent crashes, bugs en het gebruik van de app.
|
||||||
|
<h2 class="font-bold mt-5">AVG</h2>
|
||||||
|
Sinds 25 mei 2018 is de Algemene verordening gegevensbescherming (AVG) van toepassing waardoor elke vereniging helder moet maken wat zij doen om de privacy van persoonsgegevens te waarborgen. U kunt <a href="https://www.reddingsbrigadewaddinxveen.nl/Doc/201809%20-%20Privacyverklaring%20WRB.pdf" class="underline">hier</a> onze privacy verklaring vinden.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-xl ml-2 font-bold"></h1>
|
||||||
|
<div class="container">
|
||||||
|
<div class="item break-words ">
|
||||||
|
Registration Token: <b>{{ userStore.registrationToken }}</b>
|
||||||
|
</div>
|
||||||
|
<div class="divider" />
|
||||||
|
<div class="item break-words ">
|
||||||
|
User ID: <b>{{ userStore.userData.id }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Meer Informatie',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
</script>
|
235
frontend/pages/wedstrijd/addcontest.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModelForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Deelnemer Toevoegen</h1>
|
||||||
|
|
||||||
|
<label class="font-bold text-default">Deelnemer</label>
|
||||||
|
<select :disabled="modelData.edit" required v-model="modelData.relatiecode" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||||
|
<option v-if="!modelData.edit" v-for="user in contestStore.competitors.filter(x => !contest.events[modelData.event].competitors.map(y => y.relatiecode).includes(x.relatiecode))" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
||||||
|
<option v-else v-for="user in competitors" :value="user.relatiecode">{{ user.name}} ({{ user.relatiecode }})</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label class="font-bold text-default">Tijd</label>
|
||||||
|
<div class="mb-1">
|
||||||
|
<input v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||||
|
<input v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||||
|
<input v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-5">
|
||||||
|
<input type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||||
|
<span class="text-default">Diskwalificatie</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="font-bold">Info (Optioneel)</label>
|
||||||
|
<input v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||||
|
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn" :value="modelData.edit ? 'Bewerken' : 'Toevoegen'" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-5 mx-auto p-2 w-full max-w-md">
|
||||||
|
<form @submit.prevent="submitContestForm" class="flex flex-col">
|
||||||
|
<label class="font-bold">Locatie Wedstrijd</label>
|
||||||
|
<input v-model="contest.location" required="true" class="input mb-5 " type="text">
|
||||||
|
|
||||||
|
<label class="font-bold">Type Zwembad</label>
|
||||||
|
<select v-model="contest.type" required="true" class="input mb-5 " type="text">
|
||||||
|
<option value="50m">50 Meter</option>
|
||||||
|
<option value="25m">25 Meter</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label class="font-bold">Datum</label>
|
||||||
|
<input v-model="contest.date" required="true" class="input w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||||
|
|
||||||
|
<label class="font-bold">Onderdelen</label>
|
||||||
|
<div class="flex flex-col gap-y-3">
|
||||||
|
<div v-if="contestStore.competitors[0]" v-for="event in contest.events" class="container p-2">
|
||||||
|
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||||
|
<h2 class="font-bold">{{ event.name }}</h2>
|
||||||
|
<Icon size="1.2em" name="ion:arrow-down-b" class="ml-auto my-auto mr-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||||
|
</div>
|
||||||
|
<div v-if="event.open" class="mt-2">
|
||||||
|
<table class="table-fixed text-left w-full even:bg-gray-500">
|
||||||
|
<thead class="font-bold">
|
||||||
|
<tr>
|
||||||
|
<th class="w-3/6">Naam</th>
|
||||||
|
<th class="w-2/6">Tijd</th>
|
||||||
|
<th class="w-1/6">DSQ</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr @click="handleModel(competitor, event.id, true, index)" v-for="(competitor, index) in event.competitors" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||||
|
<td class="py-1 pl-1">{{ contestStore.competitors.find(x => x.relatiecode === competitor.relatiecode ).name }}</td>
|
||||||
|
<td>{{ competitor.time.minutes.toString().padStart(2, '0') }}:{{ competitor.time.seconds.toString().padStart(2, '0') }}:{{ competitor.time.milliseconds.toString().padStart(2, '0') }}</td>
|
||||||
|
<td>{{ competitor.dsq }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="contestStore.competitors.filter(x => !event.competitors.map(y => y.relatiecode).includes(x.relatiecode)).length > 0" class="even:dark:bg-neutral-700 even:bg-neutral-300">
|
||||||
|
<td @click="handleModel(null, event.id)" class="hover:cursor-pointer py-1 pl-1">+ Deelnemer toevoegen</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input :disabled="disableButtons" type="submit" class="btn mt-10 px-5 w-min mx-auto" value="Wedstrijd toevoegen" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getDocs, collection, writeBatch, doc, getFirestore } from "firebase/firestore"
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Wedstrijd Toevoegen',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const contestStore = useContestStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const db = getFirestore()
|
||||||
|
|
||||||
|
const showModel = ref(false)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
|
||||||
|
const modelData = ref({
|
||||||
|
relatiecode: '',
|
||||||
|
time: {
|
||||||
|
minutes: null,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const contest = ref({
|
||||||
|
location: '',
|
||||||
|
type: '',
|
||||||
|
date: Date,
|
||||||
|
events: {
|
||||||
|
obstacleSwim: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Obstacle Swim',
|
||||||
|
id: 'obstacleSwim',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarry: {
|
||||||
|
open: false,
|
||||||
|
name: '50m Manikin Carry',
|
||||||
|
id: 'manikinCarry',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
rescueMedley: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Rescue Medley',
|
||||||
|
id: 'rescueMedley',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarryWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Carry with Fins',
|
||||||
|
id: 'manikinCarryWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinTowWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Tow with Fins',
|
||||||
|
id: 'manikinTowWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
superLifesaver: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Super Lifesaver',
|
||||||
|
id: 'superLifesaver',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleModel = (competitor, event, edit, index) => {
|
||||||
|
if(!competitor) competitor = {
|
||||||
|
relatiecode: '',
|
||||||
|
time: {
|
||||||
|
minutes: null,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
modelData.value = competitor
|
||||||
|
modelData.value.event = event
|
||||||
|
modelData.value.edit = edit
|
||||||
|
modelData.value.index = index
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModelForm = () => {
|
||||||
|
if (!modelData.value.time.minutes) modelData.value.time.minutes = 0
|
||||||
|
if (!modelData.value.time.seconds) modelData.value.time.seconds = 0
|
||||||
|
if (!modelData.value.time.milliseconds) modelData.value.time.milliseconds = 0
|
||||||
|
const index = modelData.value.index
|
||||||
|
const edit = modelData.value.edit
|
||||||
|
|
||||||
|
delete modelData.value.index
|
||||||
|
delete modelData.value.edit
|
||||||
|
|
||||||
|
if (!edit) contest.value.events[modelData.value.event].competitors.push(modelData.value)
|
||||||
|
|
||||||
|
showModel.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitContestForm = async () => {
|
||||||
|
disableButtons.value = true
|
||||||
|
|
||||||
|
const batch = writeBatch(db)
|
||||||
|
|
||||||
|
Object.values(contest.value.events).forEach(event => {
|
||||||
|
event.competitors.forEach(competitor => {
|
||||||
|
const combinedTime = competitor.time.minutes.toString().padStart(2, '0') + competitor.time.seconds.toString().padStart(2, '0') + competitor.time.milliseconds.toString().padStart(2, '0')
|
||||||
|
|
||||||
|
|
||||||
|
const docRef = doc(collection(db, 'timings'))
|
||||||
|
batch.set(docRef, {
|
||||||
|
relatiecode: competitor.relatiecode,
|
||||||
|
contest: { location: contest.value.location, date: new Date(contest.value.date), type: contest.value.type },
|
||||||
|
event: event.id,
|
||||||
|
time: { minutes: competitor.time.minutes.toString().padStart(2, '0'), seconds: competitor.time.seconds.toString().padStart(2, '0'), milliseconds: competitor.time.milliseconds.toString().padStart(2, '0'), combined: combinedTime },
|
||||||
|
dsq: competitor.dsq,
|
||||||
|
info: competitor.info || ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await batch.commit()
|
||||||
|
disableButtons.value = false
|
||||||
|
|
||||||
|
toast.success('Wedstrijd is toegevoegd')
|
||||||
|
navigateTo('/wedstrijd')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
contestStore.getCompetitors()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
263
frontend/pages/wedstrijd/alltimes.vue
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModalForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
|
||||||
|
|
||||||
|
<label class="font-bold">Locatie</label>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
|
||||||
|
|
||||||
|
<label class="font-bold">Type zwembad</label>
|
||||||
|
<select :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||||
|
<option value="50m">50 Meter</option>
|
||||||
|
<option value="25m">25 Meter</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label class="font-bold">Datum</label>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||||
|
|
||||||
|
<label class="font-bold">Tijd</label>
|
||||||
|
<div class="mb-1">
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-5">
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||||
|
<span class="text-default">Diskwalificatie</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="font-bold">Info </label>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||||
|
|
||||||
|
<input v-if="userStore.userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div v-if="contestStore.timings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
|
||||||
|
<div class="flex gap-x-5">
|
||||||
|
<div class="relative">
|
||||||
|
<button @click.stop="showDeelnemersDropdown = !showDeelnemersDropdown" class="btn">Deelnemers <Icon size="1.2em" name="ion:arrow-down-b" /></button>
|
||||||
|
<div v-if="showDeelnemersDropdown" v-on-click-outside.bubble="handleDeelnemersDropdown" class="w-48 mt-2 container absolute rounded-lg shadow p-3 overflow-y-auto">
|
||||||
|
<ul class="space-y-2 text-default">
|
||||||
|
<li v-for="competitor in contestStore.competitors" @click="competitor.checked = !competitor.checked" class="flex gap-x-1 items-center hover:cursor-pointer">
|
||||||
|
<input v-model="competitor.checked" type="checkbox" class="checkbox">
|
||||||
|
<label class="hover:cursor-pointer">{{ competitor.name }}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative">
|
||||||
|
<button @click.stop="showPropertiesDropdown = !showPropertiesDropdown" class="btn">Eigenschappen <Icon size="1.2em" name="ion:arrow-down-b" /></button>
|
||||||
|
<div v-if="showPropertiesDropdown" v-on-click-outside.bubble="handlePropertiesDropdown" class="w-48 mt-2 container absolute rounded-lg shadow p-3">
|
||||||
|
<ul class="space-y-2 text-default">
|
||||||
|
<li v-for="property in Object.values(properties)" @click="property.enabled = !property.enabled" class="flex gap-x-1 items-center hover:cursor-pointer">
|
||||||
|
<input v-model="property.enabled" type="checkbox" class="checkbox">
|
||||||
|
<label class="hover:cursor-pointer">{{ property.name }}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-for="event in events" class="container w-full max-w-3xl py-2 px-4">
|
||||||
|
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||||
|
<h2 class="font-bold mr-auto">{{ event.name }}</h2>
|
||||||
|
<span v-if="contestStore.filteredTimings.filter(a => a.event === event.id).length > 0" class="">
|
||||||
|
<span>
|
||||||
|
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }} |
|
||||||
|
</span>
|
||||||
|
<span class="hidden md:inline-block mr-1">
|
||||||
|
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.type }} |
|
||||||
|
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.date.toLocaleDateString('nl-NL') }} |
|
||||||
|
{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].contest.location }} |
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span v-else class="">Geen tijd</span>
|
||||||
|
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||||
|
</div>
|
||||||
|
<div v-if="event.open" class="mt-2">
|
||||||
|
<table class="table-fixed text-left w-full even:bg-gray-500">
|
||||||
|
<thead class="font-bold">
|
||||||
|
<tr>
|
||||||
|
<th v-for="property in Object.values(properties).filter(a => a.enabled === true)">{{ property.name }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr @click="handleModel(time, event, index)" v-for="(time, index) in contestStore.filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||||
|
<td v-if="properties.time.enabled" class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td>
|
||||||
|
<td v-if="properties.date.enabled">{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
|
||||||
|
<td v-if="properties.name.enabled" class="overflow-hidden whitespace-nowrap truncate">{{ contestStore.competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[1] + ' ' + contestStore.competitors.filter(a => a.relatiecode === time.relatiecode)[0].name.split(', ')[0] }}</td>
|
||||||
|
<td v-if="properties.type.enabled">{{ time.contest.type }}</td>
|
||||||
|
<td v-if="properties.location.enabled">{{ time.contest.location }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { collection, query, getDocs, doc, updateDoc } from "firebase/firestore";
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
import { vOnClickOutside} from '@vueuse/components'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Brigade Tijden',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const contestStore = useContestStore()
|
||||||
|
|
||||||
|
const showModel = ref(false)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
const showDeelnemersDropdown = ref(false)
|
||||||
|
const showPropertiesDropdown = ref(false)
|
||||||
|
|
||||||
|
const handleDeelnemersDropdown = () => {
|
||||||
|
showDeelnemersDropdown.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePropertiesDropdown = () => {
|
||||||
|
showPropertiesDropdown.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelData = ref({
|
||||||
|
time: {
|
||||||
|
minutes: null,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
contest: {
|
||||||
|
type: '',
|
||||||
|
location: '',
|
||||||
|
date: Date,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const properties = ref({
|
||||||
|
time: {
|
||||||
|
enabled: true,
|
||||||
|
id: 'time',
|
||||||
|
name: 'Tijd',
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
enabled: true,
|
||||||
|
id: 'date',
|
||||||
|
name: 'Datum',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
enabled: true,
|
||||||
|
id: 'name',
|
||||||
|
name: 'Naam',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
enabled: false,
|
||||||
|
id: 'type',
|
||||||
|
name: 'Type zwembad',
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
enabled: false,
|
||||||
|
id: 'location',
|
||||||
|
name: 'Locatie',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const events = ref({
|
||||||
|
obstacleSwim: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Obstacle Swim',
|
||||||
|
id: 'obstacleSwim',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarry: {
|
||||||
|
open: false,
|
||||||
|
name: '50m Manikin Carry',
|
||||||
|
id: 'manikinCarry',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
rescueMedley: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Rescue Medley',
|
||||||
|
id: 'rescueMedley',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarryWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Carry with Fins',
|
||||||
|
id: 'manikinCarryWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinTowWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Tow with Fins',
|
||||||
|
id: 'manikinTowWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
superLifesaver: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Super Lifesaver',
|
||||||
|
id: 'superLifesaver',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await contestStore.getCompetitors()
|
||||||
|
await contestStore.getTimings()
|
||||||
|
|
||||||
|
contestStore.selectCompetitors('all')
|
||||||
|
})
|
||||||
|
|
||||||
|
const dateToYYYYMMDD = (d) => {
|
||||||
|
return d && new Date(d.getTime()-(d.getTimezoneOffset()*60*1000)).toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModel = (competitor, e, index) => {
|
||||||
|
modelData.value = competitor
|
||||||
|
modelData.value.index = index
|
||||||
|
modelData.value.eventId = e.id
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModalForm = async () => {
|
||||||
|
if (!modelData.value.time.minutes) modelData.value.time.minutes = 0
|
||||||
|
if (!modelData.value.time.seconds) modelData.value.time.seconds = 0
|
||||||
|
if (!modelData.value.time.milliseconds) modelData.value.time.milliseconds = 0
|
||||||
|
const id = modelData.value.id
|
||||||
|
|
||||||
|
delete modelData.value.index
|
||||||
|
delete modelData.value.eventId
|
||||||
|
delete modelData.value.id
|
||||||
|
|
||||||
|
disableButtons.value = true
|
||||||
|
const combinedTime = modelData.value.time.minutes.toString().padStart(2, '0') + modelData.value.time.seconds.toString().padStart(2, '0') + modelData.value.time.milliseconds.toString().padStart(2, '0')
|
||||||
|
modelData.value.time.combinedTime = combinedTime
|
||||||
|
|
||||||
|
const docRef = doc(db, "timings", id);
|
||||||
|
|
||||||
|
await updateDoc(docRef, modelData.value);
|
||||||
|
|
||||||
|
toast.success('Tijd is bewerkt')
|
||||||
|
showModel.value = false
|
||||||
|
disableButtons.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
28
frontend/pages/wedstrijd/index.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col justify-center items-center px-2 overflow-hidden">
|
||||||
|
<div class="container w-full max-w-md">
|
||||||
|
<NuxtLink to="/wedstrijd/owntimes" class="rounded-t item-hover py-2 flex items-center">
|
||||||
|
<span>Eigen Tijden</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="divider" />
|
||||||
|
<NuxtLink to="/wedstrijd/alltimes" class="item-hover py-2 flex items-center">
|
||||||
|
<span>Brigade Tijden</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
<div v-if="userStore.userData.wedstrijdAdmin" class="divider" />
|
||||||
|
<NuxtLink v-if="userStore.userData.wedstrijdAdmin" to="/wedstrijd/addcontest" class="rounded-b item-hover py-2 flex items-center">
|
||||||
|
<span>Tijden Toevoegen</span>
|
||||||
|
<Icon class="ml-auto" size="2em" name="ion:arrow-forward"/>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Wedstrijd'
|
||||||
|
})
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
</script>
|
190
frontend/pages/wedstrijd/owntimes.vue
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
<template>
|
||||||
|
<div @click.self="showModel = false" v-if="showModel" class="fixed flex justify-center items-center h-screen w-full bg-black top-0 left-0 z-50 bg-opacity-50" >
|
||||||
|
<form @submit.prevent="submitModalForm" class="dark:bg-neutral-800 bg-neutral-200 p-10 rounded-xl flex flex-col w-full max-w-sm">
|
||||||
|
<h1 class="font-bold text-center text-lg mb-5">Tijd {{ modelData.eventName }}</h1>
|
||||||
|
|
||||||
|
<label class="font-bold">Locatie</label>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.location" type="text" class="input dark:bg-neutral-700 bg-neutral-300 mb-5" />
|
||||||
|
|
||||||
|
<label class="font-bold">Datum</label>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" :value="dateToYYYYMMDD(modelData.contest.date)" @input="modelData.contest.date = $event.target.valueAsDate" required="true" class="input dark:bg-neutral-700 bg-neutral-300 w-min hover:cursor-pointer pr-0 mb-5 " type="date">
|
||||||
|
|
||||||
|
<label class="font-bold">Type zwembad</label>
|
||||||
|
<select :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.contest.type" required="true" class="input dark:bg-neutral-700 bg-neutral-300 mb-5">
|
||||||
|
<option value="50m">50 Meter</option>
|
||||||
|
<option value="25m">25 Meter</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label class="font-bold">Tijd</label>
|
||||||
|
<div class="mb-1">
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.minutes" type="number" step="1" min="0" max="99" placeholder="mm" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.seconds" type="number" step="1" min="0" max="99" placeholder="ss" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
<span class="text-default text-xl font-bold mx-1">:</span>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.time.milliseconds" type="number" step="1" min="0" max="99" placeholder="ms" class="input dark:bg-neutral-700 bg-neutral-300 w-10 text-center p-1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center mb-5">
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" type="checkbox" v-model="modelData.dsq" class="mr-1 checkbox">
|
||||||
|
<span class="text-default">Diskwalificatie</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="font-bold">Info </label>
|
||||||
|
<input :disabled="!userStore.userData.wedstrijdAdmin" v-model="modelData.info" type="text" placeholder="Bijv. Een diskwalificatie" class="input dark:bg-neutral-700 bg-neutral-300 mb-10" />
|
||||||
|
|
||||||
|
<input v-if="userStore.userData.wedstrijdAdmin" :disabled="disableButtons" type="submit" class="btn" value="Bewerken" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div v-if="contestStore.filteredTimings[0]" class="flex flex-col justify-center items-center gap-y-3 px-2 overflow-hidden">
|
||||||
|
<div v-for="event in events" class="container w-full max-w-md py-2 px-4">
|
||||||
|
<div @click="event.open = !event.open" class="flex hover:cursor-pointer">
|
||||||
|
<h2 class="font-bold">{{ event.name }}</h2>
|
||||||
|
<span v-if="contestStore.filteredTimings.filter(a => a.event === event.id)[0]" class="ml-auto">{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.minutes }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.seconds }}:{{ contestStore.filteredTimings.filter(a => a.event === event.id)[0].time.milliseconds }}</span>
|
||||||
|
<span v-else class="ml-auto">Geen tijd</span>
|
||||||
|
<Icon size="1.2em" name="ion:arrow-down-b" class="my-auto ml-2 transition-all" :class="{'rotate-180' : event.open }" />
|
||||||
|
</div>
|
||||||
|
<div v-if="event.open" class="mt-2">
|
||||||
|
<table class="table-fixed text-left w-full even:bg-gray-500">
|
||||||
|
<thead class="font-bold">
|
||||||
|
<tr>
|
||||||
|
<th class="w-3/7">Tijd</th>
|
||||||
|
<th class="w-1/7">Datum</th>
|
||||||
|
<th class="w-3/7">Type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr @click="handleModel(time, event, index)" v-for="(time, index) in contestStore.filteredTimings.filter(a => a.event === event.id)" class="even:dark:bg-neutral-700 even:bg-neutral-300 hover:cursor-pointer">
|
||||||
|
<td class="pl-1" :class="time.dsq ? 'line-through' : ''">{{ time.time.minutes }}:{{ time.time.seconds }}:{{ time.time.milliseconds}}</td>
|
||||||
|
<td>{{ time.contest.date.toLocaleDateString('nl-NL') }}</td>
|
||||||
|
<td>{{ time.contest.type }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { collection, query, where, getDocs, doc, updateDoc} from "firebase/firestore";
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
title: 'Eigen Tijden',
|
||||||
|
key: 'back'
|
||||||
|
})
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const contestStore = useContestStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const showModel = ref(false)
|
||||||
|
const disableButtons = ref(false)
|
||||||
|
|
||||||
|
const modelData = ref({
|
||||||
|
time: {
|
||||||
|
minutes: null,
|
||||||
|
seconds: null,
|
||||||
|
milliseconds: null,
|
||||||
|
},
|
||||||
|
dsq: false,
|
||||||
|
info: '',
|
||||||
|
contest: {
|
||||||
|
type: '',
|
||||||
|
location: '',
|
||||||
|
date: '',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const events = ref({
|
||||||
|
obstacleSwim: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Obstacle Swim',
|
||||||
|
id: 'obstacleSwim',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarry: {
|
||||||
|
open: false,
|
||||||
|
name: '50m Manikin Carry',
|
||||||
|
id: 'manikinCarry',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
rescueMedley: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Rescue Medley',
|
||||||
|
id: 'rescueMedley',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinCarryWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Carry with Fins',
|
||||||
|
id: 'manikinCarryWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
manikinTowWithFins: {
|
||||||
|
open: false,
|
||||||
|
name: '100m Manikin Tow with Fins',
|
||||||
|
id: 'manikinTowWithFins',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
superLifesaver: {
|
||||||
|
open: false,
|
||||||
|
name: '200m Super Lifesaver',
|
||||||
|
id: 'superLifesaver',
|
||||||
|
competitors: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await contestStore.getTimings()
|
||||||
|
contestStore.selectCompetitors('user', userStore.userData.relatiecodes)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const handleModel = (competitor, e, index) => {
|
||||||
|
modelData.value = competitor
|
||||||
|
modelData.value.index = index
|
||||||
|
modelData.value.eventId = e.id
|
||||||
|
showModel.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateToYYYYMMDD = (d) => {
|
||||||
|
return d && new Date(d.getTime()-(d.getTimezoneOffset()*60*1000)).toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitModalForm = async () => {
|
||||||
|
if (!modelData.value.time.minutes) modelData.value.time.minutes = 0
|
||||||
|
if (!modelData.value.time.seconds) modelData.value.time.seconds = 0
|
||||||
|
if (!modelData.value.time.milliseconds) modelData.value.time.milliseconds = 0
|
||||||
|
const id = modelData.value.id
|
||||||
|
|
||||||
|
delete modelData.value.index
|
||||||
|
delete modelData.value.eventId
|
||||||
|
delete modelData.value.id
|
||||||
|
|
||||||
|
disableButtons.value = true
|
||||||
|
const combinedTime = modelData.value.time.minutes.toString().padStart(2, '0') + modelData.value.time.seconds.toString().padStart(2, '0') + modelData.value.time.milliseconds.toString().padStart(2, '0')
|
||||||
|
modelData.value.time.combinedTime = combinedTime
|
||||||
|
|
||||||
|
const docRef = doc(db, "timings", id);
|
||||||
|
|
||||||
|
await updateDoc(docRef, modelData.value);
|
||||||
|
|
||||||
|
toast.success('Tijd is bewerkt')
|
||||||
|
showModel.value = false
|
||||||
|
disableButtons.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
</style>
|
23
frontend/plugins/firebase.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { initializeApp } from "firebase/app";
|
||||||
|
import { getMessaging } from "firebase/messaging";
|
||||||
|
import { getAnalytics } from "firebase/analytics";
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
const firebaseConfig = {
|
||||||
|
apiKey: "AIzaSyCtHFyfCRkBt8MX5LPFogBi8ssKSypkW0g",
|
||||||
|
authDomain: "wrbapp.firebaseapp.com",
|
||||||
|
projectId: "wrbapp",
|
||||||
|
storageBucket: "wrbapp.appspot.com",
|
||||||
|
messagingSenderId: "160377508482",
|
||||||
|
appId: "1:160377508482:web:f651ccf2b242daf4879a9b",
|
||||||
|
measurementId: "G-31HEXDSVPZ"
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = initializeApp(firebaseConfig);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const messaging = getMessaging(app);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const analytics = getAnalytics(app);
|
||||||
|
})
|
6
frontend/plugins/toast.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Toast from "vue-toastification";
|
||||||
|
import "vue-toastification/dist/index.css";
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.use(Toast)
|
||||||
|
})
|
BIN
frontend/public/android/android-launchericon-144-144.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
frontend/public/android/android-launchericon-192-192.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
frontend/public/android/android-launchericon-48-48.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
frontend/public/android/android-launchericon-512-512.png
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
frontend/public/android/android-launchericon-72-72.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
frontend/public/android/android-launchericon-96-96.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
33
frontend/public/firebase-messaging-sw.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
self.addEventListener('notificationclick', (event) => {
|
||||||
|
console.log('On notification click: ', event.notification.tag);
|
||||||
|
event.notification.close();
|
||||||
|
|
||||||
|
// This looks to see if the current is already open and
|
||||||
|
// focuses if it is
|
||||||
|
event.waitUntil(clients.matchAll({
|
||||||
|
type: "window"
|
||||||
|
}).then((clientList) => {
|
||||||
|
for (const client of clientList) {
|
||||||
|
if (client.url === '/news' && 'focus' in client)
|
||||||
|
return client.focus();
|
||||||
|
}
|
||||||
|
if (clients.openWindow)
|
||||||
|
return clients.openWindow('/news');
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
importScripts('https://www.gstatic.com/firebasejs/9.2.0/firebase-app-compat.js');
|
||||||
|
importScripts('https://www.gstatic.com/firebasejs/9.2.0/firebase-messaging-compat.js');
|
||||||
|
|
||||||
|
const firebaseConfig = {
|
||||||
|
apiKey: "AIzaSyCtHFyfCRkBt8MX5LPFogBi8ssKSypkW0g",
|
||||||
|
authDomain: "wrbapp.firebaseapp.com",
|
||||||
|
projectId: "wrbapp",
|
||||||
|
storageBucket: "wrbapp.appspot.com",
|
||||||
|
messagingSenderId: "160377508482",
|
||||||
|
appId: "1:160377508482:web:f651ccf2b242daf4879a9b",
|
||||||
|
measurementId: "G-31HEXDSVPZ"
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = firebase.initializeApp(firebaseConfig);
|
||||||
|
const messaging = firebase.messaging();
|
BIN
frontend/public/ios/100.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
frontend/public/ios/1024.png
Normal file
After Width: | Height: | Size: 662 KiB |
BIN
frontend/public/ios/114.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
frontend/public/ios/120.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
frontend/public/ios/128.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
frontend/public/ios/144.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
frontend/public/ios/152.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
frontend/public/ios/16.png
Normal file
After Width: | Height: | Size: 1.0 KiB |