initial commit

Can connect with Discord, login, logout, and delete account
This commit is contained in:
2024-02-10 10:55:11 +01:00
commit 52853ea99b
67 changed files with 6251 additions and 0 deletions

38
src/App.vue Normal file
View File

@@ -0,0 +1,38 @@
<script setup lang="ts">
import { RouterView } from 'vue-router';
import AppHeader from './components/AppHeader.vue';
import AppFooter from './components/AppFooter.vue';
</script>
<template>
<AppHeader />
<main class="flex-col-center gap-3rem">
<RouterView />
</main>
<AppFooter />
</template>
<style scoped lang="less">
main {
justify-content: flex-start;
margin: 5rem;
align-content: center;
padding-top: 4.5rem !important;
padding-bottom: 7rem !important;
}
header {
position: fixed;
top: 0;
width: 100%;
}
@media screen {
@media (max-width: 1000px) {
main {
margin: 3% 5%;
overflow: visible !important;
}
}
}
</style>

24
src/assets/_layouts.less Normal file
View File

@@ -0,0 +1,24 @@
.hero {
display: block;
padding: 3rem 50%;
}
.two-col-img-text {
// must contain a <figure> or <img> and something else.
.flex-row-center;
padding: 1rem;
gap: 4rem;
width: 100%;
* {
grid-area: text;
}
figure,
img {
grid-area: figure;
max-width: 100%;
max-height: 10rem;
border-radius: 2rem;
}
}

64
src/assets/_mixins.less Normal file
View File

@@ -0,0 +1,64 @@
.flex {
display: flex;
}
.flex-col {
.flex;
flex-direction: column;
}
.flex-row {
.flex;
flex-direction: row;
}
.flex-center {
align-items: center;
}
.flex-row-center {
.flex-row;
.flex-center;
}
.flex-col-center {
.flex-col;
.flex-center;
}
.flex-spread {
justify-content: space-between;
}
.themed(@property, @light, @dark) {
@{property}: @light;
html.dark & {
@{property}: @dark;
}
}
.gen-gap(@n, @i: 1) when (@i =< @n) {
.gap-@{i}rem {
gap: (@i * 1rem);
}
.gen-gap(@n, (@i + 1));
}
.gen-gap(10);
.gen-grid-two-col-width(@right: 10, @left: 90) when (@right =< 90) {
.two-col-@{left}-@{right} {
grid-template-columns: (@left * 1%) (@right * 1%);
}
.gen-grid-two-col-width((@right + 10), (@left - 10));
}
.gen-grid-two-col-width();
.gen-margin(@n, @i: 1) when (@i =< @n) {
.margin-@{i}rem {
margin: (@i * 1rem);
}
.gen-margin(@n, (@i + 1));
}
.gen-margin(10);

167
src/assets/_reset.less Normal file
View File

@@ -0,0 +1,167 @@
// From https://byby.dev/normalize-css on 2024-01-22
html {
line-height: 1.15;
-webkit-text-size-adjust: 100%;
min-height: 100%;
position: relative;
}
body {
margin: 0;
}
main {
display: block;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
pre,
code,
kdb,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
a {
background-color: transparent;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sub {
top: -0.5em;
}
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
[type='button'],
[type='reset'],
[type='submit'] {
appearance: button;
-webkit-appearance: button;
}
button:-moz-focusring,
[type='button']:-moz-focusring,
[type='reset']:-moz-focusring,
[type='submit']:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
padding: 0.35em 0.75em 0.625em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
progress {
vertical-align: baseline;
}
textarea {
overflow: auto;
}
[type='checkbox'],
[type='radio'] {
box-sizing: border-box;
padding: 0;
}
[type='number']::-webkit-inner-spin-button,
[type='number']::-webkit-outer-spin-button {
height: auto;
}
[type='search'] {
appearance: textfield;
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type='search']::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
details {
display: block;
}
summary {
display: list-item;
}
template {
display: none;
}
[hidden] {
display: none;
}

165
src/assets/_theme.less Normal file
View File

@@ -0,0 +1,165 @@
@import '_mixins';
@light-text: #081611; // 925
@light-background: #e4f6ed; // 75
@light-primary: #276d4f; // 750
@light-secondary: #8cd1d4; // 350
@light-accent: #4195af; // 550
@light-text-50: #ecf8f4;
@light-text-100: #daf1e9;
@light-text-200: #b4e4d3;
@light-text-300: #8fd6bd;
@light-text-400: #69c9a7;
@light-text-500: #44bb91;
@light-text-600: #369674;
@light-text-700: #297057;
@light-text-800: #1b4b3a;
@light-text-900: #0e251d;
@light-text-950: #07130f;
@light-background-50: #ecf9f2;
@light-background-100: #d9f2e6;
@light-background-200: #b3e6cc;
@light-background-300: #8cd9b3;
@light-background-400: #66cc99;
@light-background-500: #40bf80;
@light-background-600: #339966;
@light-background-700: #26734d;
@light-background-800: #194d33;
@light-background-900: #0d261a;
@light-background-950: #06130d;
@light-primary-50: #ecf8f3;
@light-primary-100: #daf1e7;
@light-primary-200: #b4e4cf;
@light-primary-300: #8fd6b7;
@light-primary-400: #69c99f;
@light-primary-500: #44bb87;
@light-primary-600: #36966c;
@light-primary-700: #297051;
@light-primary-800: #1b4b36;
@light-primary-900: #0e251b;
@light-primary-950: #07130e;
@light-secondary-50: #ecf8f8;
@light-secondary-100: #daf0f1;
@light-secondary-200: #b5e1e3;
@light-secondary-300: #8fd2d6;
@light-secondary-400: #6ac3c8;
@light-secondary-500: #45b4ba;
@light-secondary-600: #379095;
@light-secondary-700: #296c70;
@light-secondary-800: #1c484a;
@light-secondary-900: #0e2425;
@light-secondary-950: #071213;
@light-accent-50: #ecf5f8;
@light-accent-100: #daecf1;
@light-accent-200: #b5d9e3;
@light-accent-300: #8fc5d6;
@light-accent-400: #6ab2c8;
@light-accent-500: #459fba;
@light-accent-600: #377f95;
@light-accent-700: #295f70;
@light-accent-800: #1c404a;
@light-accent-900: #0e2025;
@light-accent-950: #071013;
@light-gray-10: rgba(black, 0.1);
@light-gray-20: rgba(black, 0.2);
@light-gray-30: rgba(black, 0.3);
@light-gray-40: rgba(black, 0.4);
@light-gray-50: rgba(black, 0.5);
@light-gray-60: rgba(black, 0.6);
@light-gray-70: rgba(black, 0.7);
@light-gray-80: rgba(black, 0.8);
@light-gray-90: rgba(black, 0.9);
@light-gray-100: rgba(black, 1);
@dark-text: #e9f7f2; // 1000
@dark-background: #091b12; // 75
@dark-primary: #92d8ba;
@dark-secondary: #2b6f73;
@dark-accent: #50a5be;
@dark-text-50: #07130f;
@dark-text-100: #0e251d;
@dark-text-200: #1b4b3a;
@dark-text-300: #297057;
@dark-text-400: #369674;
@dark-text-500: #44bb91;
@dark-text-600: #69c9a7;
@dark-text-700: #8fd6bd;
@dark-text-800: #b4e4d3;
@dark-text-900: #daf1e9;
@dark-text-950: #ecf8f4;
@dark-background-50: #06130d;
@dark-background-100: #0d261a;
@dark-background-200: #194d33;
@dark-background-300: #26734d;
@dark-background-400: #339966;
@dark-background-500: #40bf80;
@dark-background-600: #66cc99;
@dark-background-700: #8cd9b3;
@dark-background-800: #b3e6cc;
@dark-background-900: #d9f2e6;
@dark-background-950: #ecf9f2;
@dark-primary-50: #07130e;
@dark-primary-100: #0e251b;
@dark-primary-200: #1b4b36;
@dark-primary-300: #297051;
@dark-primary-400: #36966c;
@dark-primary-500: #44bb87;
@dark-primary-600: #69c99f;
@dark-primary-700: #8fd6b7;
@dark-primary-800: #b4e4cf;
@dark-primary-900: #daf1e7;
@dark-primary-950: #ecf8f3;
@dark-secondary-50: #071213;
@dark-secondary-100: #0e2425;
@dark-secondary-200: #1c484a;
@dark-secondary-300: #296c70;
@dark-secondary-400: #379095;
@dark-secondary-500: #45b4ba;
@dark-secondary-600: #6ac3c8;
@dark-secondary-700: #8fd2d6;
@dark-secondary-800: #b5e1e3;
@dark-secondary-900: #daf0f1;
@dark-secondary-950: #ecf8f8;
@dark-accent-50: #071013;
@dark-accent-100: #0e2025;
@dark-accent-200: #1c404a;
@dark-accent-300: #295f70;
@dark-accent-400: #377f95;
@dark-accent-500: #459fba;
@dark-accent-600: #6ab2c8;
@dark-accent-700: #8fc5d6;
@dark-accent-800: #b5d9e3;
@dark-accent-900: #daecf1;
@dark-accent-950: #ecf5f8;
@dark-gray-10: rgba(white, 0.1);
@dark-gray-20: rgba(white, 0.2);
@dark-gray-30: rgba(white, 0.3);
@dark-gray-40: rgba(white, 0.4);
@dark-gray-50: rgba(white, 0.5);
@dark-gray-60: rgba(white, 0.6);
@dark-gray-70: rgba(white, 0.7);
@dark-gray-80: rgba(white, 0.8);
@dark-gray-90: rgba(white, 0.9);
@dark-gray-100: rgba(white, 1);
html {
background-color: @light-background;
color: @light-text;
&.dark {
background-color: @dark-background;
color: @dark-text;
}
}

View File

@@ -0,0 +1,80 @@
@import 'titles';
@import '../_mixins';
button,
.button {
background-color: @light-primary;
color: @light-background;
border: none;
padding: 0.7rem 1.2rem;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1rem;
border-radius: 10rem;
transition: all 0.2s ease-in-out;
cursor: pointer;
.themed(background-color, @light-primary, @dark-primary);
.themed(color, @light-background, @dark-background);
&:hover {
transition: all 0.2s ease-in-out;
.themed(box-shadow,
0.1rem 0.1rem 2rem rgba(@light-accent, 0.8),
0.1rem 0.1rem 2rem rgba(@dark-accent, 0.8));
}
&.no-shadow {
box-shadow: none !important;
}
&.accent {
background-color: @light-accent;
html.dark & {
background-color: @dark-accent;
}
}
&.secondary {
.themed(background-color, @light-secondary, @dark-secondary);
.themed(color, @light-text, @dark-text);
}
&.faded {
.themed(background-color, @light-background-300, @dark-background-800);
.themed(color, @light-text, @dark-background);
}
&.disabled {
.themed(background-color, @light-background-100, @dark-background-100);
.themed(color, @light-text-600, @dark-text-600);
}
&.h2 {
padding: (@h2-size / 2) (@h2-size * (2 / 3));
}
&.h3 {
padding: (@h3-size / 2) (@h3-size * (2 / 3));
}
&.h4 {
padding: (@h4-size / 2) (@h4-size * (2 / 3));
}
header & {
background-color: inherit !important;
border-radius: 0.5rem;
.themed(border, solid @light-gray-10 1px, solid @dark-gray-10 1px);
&:hover {
.themed(box-shadow,
0.1rem 0.1rem 0.5rem @light-gray-10,
0.1rem 0.1rem 0.5rem @dark-gray-10);
}
}
&.raise:hover {
transform: translate(0, -3px);
}
}

View File

@@ -0,0 +1,26 @@
@import '../_mixins';
.card {
border-radius: 2rem;
padding: 2rem;
.themed(background-color, @light-background-100, @dark-background-100);
&.more {
.themed(background-color, @light-background-200, @dark-background-200);
}
&.accent {
.themed(background-color, @light-accent, @dark-accent);
.themed(color, @light-background, @dark-background);
}
&.primary {
.themed(background-color, @light-primary, @dark-primary);
.themed(color, @light-background, @dark-background);
}
&.secondary {
.themed(background-color, @light-secondary, @dark-secondary);
.themed(color, @light-text, @dark-text);
}
}

View File

@@ -0,0 +1,26 @@
@import '../_mixins';
.highlight {
z-index: 5;
position: relative;
.flex-col;
justify-content: flex-end;
&::before {
content: '';
height: 50%;
width: 108%;
left: -4%;
display: block;
z-index: -5;
opacity: 30%;
position: absolute;
transition: all 0.3s ease-in-out;
background: linear-gradient(180deg, transparent 50%, @dark-accent 50%);
}
&:hover::before {
height: 100%;
transition: all 0.3s ease-in-out;
}
}

View File

@@ -0,0 +1,4 @@
a {
color: inherit;
text-decoration: none;
}

View File

@@ -0,0 +1,57 @@
.title {
font-family: 'Poppins', sans-serif;
font-weight: 700;
display: inline-block;
}
.h1,
h1,
.h2,
h2,
.h3,
h3,
.h4,
h4,
.h5,
h5 {
.title;
}
html {
font-size: 100%;
}
@h1-size: 4.21rem;
.h1,
h1 {
font-size: @h1-size;
}
@h2-size: 3.158rem;
.h2,
h2 {
font-size: @h2-size;
}
@h3-size: 2.369rem;
.h3,
h3 {
font-size: @h3-size;
}
@h4-size: 1.777rem;
.h4,
h4 {
font-size: @h4-size;
}
@h5-size: 1.333rem;
.h5,
h5 {
font-size: @h5-size;
}
.small,
small {
font-size: 0.75rem;
}

29
src/assets/main.less Normal file
View File

@@ -0,0 +1,29 @@
@import '_reset';
@import '@fontsource/roboto';
@import '@fontsource/poppins';
@import '_theme';
@import '_mixins';
@import '_layouts';
@import 'components/buttons';
@import 'components/cards';
@import 'components/highlight';
@import 'components/links';
body {
font-family: 'Roboto', sans-serif;
font-weight: 400;
}
* {
box-sizing: border-box;
scroll-padding-top: 3rem;
}
ul.no-style {
list-style: none;
list-style-type: none;
padding-left: 0;
margin-top: 0;
margin-bottom: 0;
}

View File

@@ -0,0 +1,26 @@
<template>
<footer class="flex-row flex-spread card">
<div id="copyright" class="small">Copyright &copy; {{ currentYear }} Lucien Cartier-Tilet</div>
<div id="source">
<a class="highlight small" href="https://labs.phundrak.com/phundrak/gege-jdr">Source code</a>
</div>
</footer>
</template>
<script setup lang="ts">
const currentYear = new Date().getFullYear();
</script>
<style scoped="" lang="less">
@import '@/assets/_theme';
footer {
margin: 2rem;
position: absolute;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
box-sizing: border-box;
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<header class="flex-row-center flex-spread">
<RouterLink class="title h4" :to="{ name: 'home' }">GéDR</RouterLink>
<div class="buttons flex-row gap-1rem">
<button @click="toggleDark()" class="secondary">{{ isDark ? 'Dark' : 'Light' }}</button>
<button v-if="!loggedIn" @click="login()" class="secondary">Login</button>
<RouterLink v-else :to="{ name: 'account' }" class="button secondary">Account</RouterLink>
</div>
</header>
</template>
<script setup lang="ts">
import { RouterLink } from 'vue-router';
import { useDark, useToggle } from '@vueuse/core';
import { usePocketbaseStore } from '@/stores/pocketbase';
import { ref } from 'vue';
import router from '@/router';
const isDark = useDark();
const toggleDark = useToggle(isDark);
const pbStore = usePocketbaseStore();
const loggedIn = ref(pbStore.loggedIn);
const login = () => {
pbStore.login().subscribe({
next: () => router.go(0),
});
};
</script>
<style scoped lang="less">
@import '@/assets/_theme';
header {
padding: 1rem 2rem;
height: 4.5rem;
.themed(background-color, @light-background, @dark-background);
.themed(border-bottom, solid @light-gray-10 1px, solid @dark-gray-10 1px);
a {
text-decoration: none;
color: inherit;
}
}
</style>

103
src/components/AppModal.vue Normal file
View File

@@ -0,0 +1,103 @@
<template>
<div class="modal-backdrop">
<div class="modal flex-col">
<header class="modal-header flex h3">
<slot name="header">Default modal title</slot>
</header>
<button class="btn-close" type="button" @click="close">x</button>
<section class="modal-body">
<slot name="body">Default modal body</slot>
</section>
<footer class="modal-footer flex-col">
<slot name="footer"></slot>
<button v-if="props.type === 'info'" class="accent" type="button" @click="close">
Fermer
</button>
<div v-else class="flex-row gap-1rem action-buttons">
<button class="faded" type="button" @click="close">Annuler</button>
<button type="button" @click="agree">OK</button>
</div>
</footer>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
type: 'info' | 'action';
}>();
const emit = defineEmits(['close', 'agree']);
const close = () => {
emit('close');
};
const agree = () => {
emit('agree');
};
</script>
<style scoped lang="less">
@import '@/assets/_theme';
@outer-padding: 3rem;
.modal-backdrop {
z-index: 10;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
.themed(background-color, @light-gray-80, @light-gray-80);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
z-index: 11;
.themed(background, @light-background, @dark-background);
.themed(color, @light-text, @dark-text);
.themed(
box-shadow,
0.2rem 0.2rem 2rem 1px @light-background-100,
0.2rem 0.2rem 4rem 1px @dark-background-300
);
overflow-x: auto;
border-radius: 1rem;
}
.modal-header,
.modal-footer {
padding: @outer-padding;
}
.modal-header {
position: relative;
border-bottom: 1px solit @dark-gray-80;
justify-content: space-between;
.themed(color, @light-primary, @dark-primary);
}
.modal-footer {
border-top: 1px solid @dark-gray-80;
justify-content: flex-end;
}
.modal-body {
position: relative;
padding: (@outer-padding / 2) @outer-padding;
max-width: 50rem;
}
.btn-close {
position: absolute;
top: 0;
right: 0;
font-weight: bold;
background: transparent;
.themed(color, @light-background, @dark-text);
}
.action-buttons {
justify-content: flex-end;
}
</style>

View File

@@ -0,0 +1,6 @@
<template>Bonjour {{ pbStore.username }}&nbsp;!</template>
<script setup="" lang="ts">
import { usePocketbaseStore } from '@/stores/pocketbase';
const pbStore = usePocketbaseStore();
</script>

View File

@@ -0,0 +1,18 @@
<template>
<div class="two-col-img-text gap-4rem">
<img alt="Logo de Gégé" src="/assets/gege.png" />
<h1>GéDR</h1>
</div>
<h2 class="card">
L&apos;application web pour vous accompagner pour votre JDR avec Gégé, le bot discord.
</h2>
<div class="flex-col-center gap-1rem">
<h3>Pourquoi&nbsp;?</h3>
<ul class="flex-row gap-1rem no-style">
<li class="card more">Accéder à ses fiche personnage</li>
<li class="card more">Faire évoluer ses personnage</li>
<li class="card more">Créer ses personnages</li>
</ul>
</div>
<button class="h4 raise margin-3rem">Se connecter avec Discord</button>
</template>

25
src/main.ts Normal file
View File

@@ -0,0 +1,25 @@
import '@/assets/main.less';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { usePocketbaseStore } from '@/stores/pocketbase';
import App from './App.vue';
import router from './router';
const app = createApp(App);
router.beforeEach((to, from, next) => {
const pbStore = usePocketbaseStore();
if (!pbStore.loggedIn && ['home', 'login'].every((path) => path != to.name)) {
next({ name: 'home' });
} else {
next();
}
});
app.use(createPinia());
app.use(router);
app.mount('#app');

19
src/router/index.ts Normal file
View File

@@ -0,0 +1,19 @@
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/account',
name: 'account',
component: () => import('@/views/AccountView.vue'),
},
],
});
export default router;

48
src/stores/pocketbase.ts Normal file
View File

@@ -0,0 +1,48 @@
import PocketBase, { BaseAuthStore, type RecordAuthResponse, type RecordModel } from 'pocketbase';
import { defineStore } from 'pinia';
import { from, map, Observable, tap } from 'rxjs';
import { computed, ref } from 'vue';
export const usePocketbaseStore = defineStore('pocketbase', () => {
const pb = new PocketBase(import.meta.env.VITE_PB_URL);
const authData = ref<RecordAuthResponse<RecordModel> | null>(null);
const authStore = computed<BaseAuthStore>(() => pb.authStore);
const loggedIn = computed<boolean>(() => pb.authStore.isValid);
const username = computed<string | null>(() => authStore.value.model?.username);
function login(): Observable<RecordAuthResponse<RecordModel>> {
return from(pb.collection('users').authWithOAuth2({ provider: 'discord' })).pipe(
map((auth) => (authData.value = auth))
);
}
function refresh(): Observable<RecordAuthResponse<RecordModel>> {
return from(pb.collection('users').authRefresh()).pipe(tap((auth) => (authData.value = auth)));
}
function logout() {
pb.authStore.clear();
authData.value = null;
}
function deleteAccount(): Observable<boolean> {
return from(pb.collection('users').delete(pb.authStore.model?.id)).pipe(
tap((deleted) => {
if (deleted) {
logout();
}
})
);
}
return {
authData,
authStore,
loggedIn,
username,
login,
refresh,
logout,
deleteAccount,
};
});

78
src/views/AccountView.vue Normal file
View File

@@ -0,0 +1,78 @@
<template>
<div class="flex-col-center">
<h1>Hello {{ username }}&nbsp;!</h1>
<div>
<h2>Actions</h2>
<ul class="no-style flex-col-center gap-1rem">
<li>
<button @click="logout()">Logout</button>
</li>
<li>
<button @click="openModal()">Delete Account</button>
</li>
</ul>
</div>
</div>
<transition name="modal-fade">
<AppModal :type="'action'" v-show="showModal" @close="closeModal()" @agree="deleteAccount">
<template v-slot:header>Suppression de compte</template>
<template v-slot:body>
<p>
Voulez-vous vraiment supprimer votre compte&nbsp;? Cette action est irréversible et
supprimera toutes les données liées à votre compte.
</p>
<p>
Vous pourrez à tout moment vous recréer un compte vierge si vous décidez à un moment
d&apos;utiliser à nouveau ce site web.
</p>
</template>
</AppModal>
</transition>
</template>
<script setup lang="ts">
import { usePocketbaseStore } from '@/stores/pocketbase';
import router from '@/router';
import AppModal from '@/components/AppModal.vue';
import { ref } from 'vue';
const pbStore = usePocketbaseStore();
const username = pbStore.username;
const showModal = ref<boolean>(false);
const logout = () => {
pbStore.logout();
router.go(0);
};
const openModal = () => {
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
};
const deleteAccount = () => {
pbStore.deleteAccount().subscribe({
next: () => {
closeModal();
pbStore.logout();
router.go(0);
},
});
};
</script>
<style scoped lang="less">
.modal-fade-enter,
.modal-fade-leave-to {
opacity: 0;
transition: opacity 0.5 ease;
}
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity 0.5 ease;
}
</style>

16
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,16 @@
<template>
<LoggedInHome v-if="pbStore.loggedIn" />
<LoggedOutHome v-else />
</template>
<script lang="ts" setup>
import { usePocketbaseStore } from '@/stores/pocketbase';
import LoggedOutHome from '@/components/LoggedOutHome.vue';
import LoggedInHome from '@/components/LoggedInHome.vue';
const pbStore = usePocketbaseStore();
pbStore.refresh().subscribe({
error: (err) => console.log('Could not refresh account data:', err),
});
</script>