Added auth management, and AccountMenuItem.vue
This commit is contained in:
parent
4293ddb157
commit
c56f8f72c2
|
@ -1,4 +1,6 @@
|
||||||
import { api } from 'src/api/main/index';
|
import { api } from 'src/api/main/index';
|
||||||
|
import { AuthStoreType } from 'stores/auth-store';
|
||||||
|
import Timeout = NodeJS.Timeout;
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -13,16 +15,38 @@ export interface TokenCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthModule {
|
class AuthModule {
|
||||||
public async getToken(credentials: TokenCredentials): Promise<string> {
|
private static readonly TOKEN_REFRESH_INTERVAL_MS = 30000;
|
||||||
|
|
||||||
|
private tokenRefreshTimer?: Timeout;
|
||||||
|
|
||||||
|
public async login(authStore: AuthStoreType, credentials: TokenCredentials) {
|
||||||
|
authStore.token = await this.fetchNewToken(credentials);
|
||||||
|
authStore.user = await this.fetchMyUser(authStore);
|
||||||
|
|
||||||
|
clearTimeout(this.tokenRefreshTimer);
|
||||||
|
this.tokenRefreshTimer = setTimeout(
|
||||||
|
() => this.refreshToken(authStore),
|
||||||
|
AuthModule.TOKEN_REFRESH_INTERVAL_MS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout(authStore: AuthStoreType) {
|
||||||
|
authStore.$reset();
|
||||||
|
clearTimeout(this.tokenRefreshTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchNewToken(credentials: TokenCredentials): Promise<string> {
|
||||||
const response = await api.post('/auth/token', credentials);
|
const response = await api.post('/auth/token', credentials);
|
||||||
return response.data.token;
|
return response.data.token;
|
||||||
}
|
}
|
||||||
public async getMyUser(token: string): Promise<User> {
|
|
||||||
const response = await api.get('/auth/me', {
|
private async refreshToken(authStore: AuthStoreType) {
|
||||||
headers: {
|
const response = await api.get('/auth/token', authStore.axiosConfig);
|
||||||
'Authorization': 'Bearer ' + token
|
authStore.token = response.data.token;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
private async fetchMyUser(authStore: AuthStoreType): Promise<User> {
|
||||||
|
const response = await api.get('/auth/me', authStore.axiosConfig);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,9 @@ class GymsModule {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRecentSubmissions(gym: GymRoutable): Promise<Array<ExerciseSubmission>> {
|
public async getRecentSubmissions(
|
||||||
|
gym: GymRoutable
|
||||||
|
): Promise<Array<ExerciseSubmission>> {
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions`
|
`/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions`
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing';
|
||||||
import { api } from 'src/api/main/index';
|
import { api } from 'src/api/main/index';
|
||||||
|
|
||||||
export enum LeaderboardTimeframe {
|
export enum LeaderboardTimeframe {
|
||||||
DAY = "DAY",
|
DAY = 'DAY',
|
||||||
WEEK = "WEEK",
|
WEEK = 'WEEK',
|
||||||
MONTH = "MONTH",
|
MONTH = 'MONTH',
|
||||||
YEAR = "YEAR",
|
YEAR = 'YEAR',
|
||||||
ALL = "ALL"
|
ALL = 'ALL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LeaderboardParams {
|
export interface LeaderboardParams {
|
||||||
|
@ -27,14 +27,16 @@ interface RequestParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
class LeaderboardsModule {
|
class LeaderboardsModule {
|
||||||
public async getLeaderboard(params: LeaderboardParams): Promise<Array<ExerciseSubmission>> {
|
public async getLeaderboard(
|
||||||
|
params: LeaderboardParams
|
||||||
|
): Promise<Array<ExerciseSubmission>> {
|
||||||
const requestParams: RequestParams = {};
|
const requestParams: RequestParams = {};
|
||||||
if (params.exerciseShortName) {
|
if (params.exerciseShortName) {
|
||||||
requestParams.exercise = params.exerciseShortName;
|
requestParams.exercise = params.exerciseShortName;
|
||||||
}
|
}
|
||||||
if (params.gyms) {
|
if (params.gyms) {
|
||||||
requestParams.gyms = params.gyms
|
requestParams.gyms = params.gyms
|
||||||
.map(gym => getGymCompoundId(gym))
|
.map((gym) => getGymCompoundId(gym))
|
||||||
.join(',');
|
.join(',');
|
||||||
}
|
}
|
||||||
if (params.timeframe) {
|
if (params.timeframe) {
|
||||||
|
|
|
@ -38,10 +38,10 @@ export enum ExerciseSubmissionStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubmissionsModule {
|
class SubmissionsModule {
|
||||||
public async getSubmission(submissionId: string): Promise<ExerciseSubmission> {
|
public async getSubmission(
|
||||||
const response = await api.get(
|
submissionId: string
|
||||||
`/submissions/${submissionId}`
|
): Promise<ExerciseSubmission> {
|
||||||
);
|
const response = await api.get(`/submissions/${submissionId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class SubmissionsModule {
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return BASE_URL + `/submissions/${submission.id}/video`
|
return BASE_URL + `/submissions/${submission.id}/video`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createSubmission(
|
public async createSubmission(
|
||||||
|
@ -60,10 +60,7 @@ class SubmissionsModule {
|
||||||
payload: ExerciseSubmissionPayload
|
payload: ExerciseSubmissionPayload
|
||||||
): Promise<ExerciseSubmission> {
|
): Promise<ExerciseSubmission> {
|
||||||
const gymId = getGymCompoundId(gym);
|
const gymId = getGymCompoundId(gym);
|
||||||
const response = await api.post(
|
const response = await api.post(`/gyms/${gymId}/submissions`, payload);
|
||||||
`/gyms/${gymId}/submissions`,
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,7 +82,9 @@ class SubmissionsModule {
|
||||||
* Asynchronous method that waits until a submission is done processing.
|
* Asynchronous method that waits until a submission is done processing.
|
||||||
* @param submissionId The submission's id.
|
* @param submissionId The submission's id.
|
||||||
*/
|
*/
|
||||||
public async waitUntilSubmissionProcessed(submissionId: string): Promise<ExerciseSubmission> {
|
public async waitUntilSubmissionProcessed(
|
||||||
|
submissionId: string
|
||||||
|
): Promise<ExerciseSubmission> {
|
||||||
let failureCount = 0;
|
let failureCount = 0;
|
||||||
let attemptCount = 0;
|
let attemptCount = 0;
|
||||||
while (failureCount < 5 && attemptCount < 60) {
|
while (failureCount < 5 && attemptCount < 60) {
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<div class="q-mx-sm">
|
||||||
|
<q-btn-dropdown
|
||||||
|
color="primary"
|
||||||
|
:label="authStore.user?.name"
|
||||||
|
v-if="authStore.loggedIn"
|
||||||
|
no-caps
|
||||||
|
icon="person"
|
||||||
|
>
|
||||||
|
<q-list>
|
||||||
|
<q-item clickable v-close-popup @click="api.auth.logout(authStore)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Log out</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
:label="$t('Login')"
|
||||||
|
v-if="!authStore.loggedIn"
|
||||||
|
no-caps
|
||||||
|
icon="person"
|
||||||
|
to="/login"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from 'stores/auth-store';
|
||||||
|
import api from 'src/api/main';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -1,7 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
expand-separator
|
expand-separator
|
||||||
:label="submission.rawWeight + ' ' + submission.weightUnit + ' x' + submission.reps + ' ' + submission.exercise.displayName"
|
:label="
|
||||||
|
submission.rawWeight +
|
||||||
|
' ' +
|
||||||
|
submission.weightUnit +
|
||||||
|
' x' +
|
||||||
|
submission.reps +
|
||||||
|
' ' +
|
||||||
|
submission.exercise.displayName
|
||||||
|
"
|
||||||
:caption="submission.submitterName"
|
:caption="submission.submitterName"
|
||||||
>
|
>
|
||||||
<q-card>
|
<q-card>
|
||||||
|
@ -24,7 +32,7 @@ import {ExerciseSubmission} from 'src/api/main/submission';
|
||||||
import api from 'src/api/main';
|
import api from 'src/api/main';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
submission: ExerciseSubmission
|
submission: ExerciseSubmission;
|
||||||
}
|
}
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<template>
|
||||||
|
<q-select
|
||||||
|
v-model="i18n.locale.value"
|
||||||
|
:options="localeOptions"
|
||||||
|
:label="$t('mainLayout.language')"
|
||||||
|
dense
|
||||||
|
borderless
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
options-dense
|
||||||
|
filled
|
||||||
|
hide-bottom-space
|
||||||
|
dark
|
||||||
|
options-dark
|
||||||
|
label-color="white"
|
||||||
|
options-selected-class="text-grey"
|
||||||
|
style="min-width: 150px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const i18n = useI18n({ useScope: 'global' });
|
||||||
|
const localeOptions = [
|
||||||
|
{ value: 'en-US', label: 'English' },
|
||||||
|
{ value: 'nl-NL', label: 'Nederlands' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -12,7 +12,7 @@ export default {
|
||||||
leaderboard: 'Leaderboard',
|
leaderboard: 'Leaderboard',
|
||||||
homePage: {
|
homePage: {
|
||||||
overview: 'Overview of this gym:',
|
overview: 'Overview of this gym:',
|
||||||
recentLifts: 'Recent Lifts'
|
recentLifts: 'Recent Lifts',
|
||||||
},
|
},
|
||||||
submitPage: {
|
submitPage: {
|
||||||
name: 'Your Name',
|
name: 'Your Name',
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default {
|
||||||
leaderboard: 'Scorebord',
|
leaderboard: 'Scorebord',
|
||||||
homePage: {
|
homePage: {
|
||||||
overview: 'Overzicht van dit sportschool:',
|
overview: 'Overzicht van dit sportschool:',
|
||||||
recentLifts: 'Recente liften'
|
recentLifts: 'Recente liften',
|
||||||
},
|
},
|
||||||
submitPage: {
|
submitPage: {
|
||||||
name: 'Jouw naam',
|
name: 'Jouw naam',
|
||||||
|
|
|
@ -16,23 +16,8 @@
|
||||||
>Gymboard</router-link
|
>Gymboard</router-link
|
||||||
>
|
>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<q-select
|
<AccountMenuItem />
|
||||||
v-model="i18n.locale.value"
|
<LocaleSelect />
|
||||||
:options="localeOptions"
|
|
||||||
:label="$t('mainLayout.language')"
|
|
||||||
dense
|
|
||||||
borderless
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
options-dense
|
|
||||||
filled
|
|
||||||
hide-bottom-space
|
|
||||||
dark
|
|
||||||
options-dark
|
|
||||||
label-color="white"
|
|
||||||
options-selected-class="text-grey"
|
|
||||||
style="min-width: 150px"
|
|
||||||
/>
|
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
||||||
|
@ -55,13 +40,8 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import LocaleSelect from 'components/LocaleSelect.vue';
|
||||||
|
import AccountMenuItem from 'components/AccountMenuItem.vue';
|
||||||
const i18n = useI18n({ useScope: 'global' });
|
|
||||||
const localeOptions = [
|
|
||||||
{ value: 'en-US', label: 'English' },
|
|
||||||
{ value: 'nl-NL', label: 'Nederlands' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const leftDrawerOpen = ref(false);
|
const leftDrawerOpen = ref(false);
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,13 @@
|
||||||
<StandardCenteredPage>
|
<StandardCenteredPage>
|
||||||
<h3>Testing Page</h3>
|
<h3>Testing Page</h3>
|
||||||
<p>
|
<p>
|
||||||
Use this page to test new functionality, before adding it to the main
|
Use this page to test new functionality, before adding it to the main app.
|
||||||
app. This page should be hidden on production.
|
This page should be hidden on production.
|
||||||
</p>
|
</p>
|
||||||
<div style="border: 3px solid red">
|
<div style="border: 3px solid red">
|
||||||
<h4>Auth Test</h4>
|
<h4>Auth Test</h4>
|
||||||
<q-btn
|
<q-btn label="Do auth" @click="doAuth()" />
|
||||||
label="Do auth"
|
<q-btn label="Logout" @click="api.auth.logout(authStore)" />
|
||||||
@click="doAuth()"
|
|
||||||
/>
|
|
||||||
<p>{{ authTestMessage }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</StandardCenteredPage>
|
</StandardCenteredPage>
|
||||||
</template>
|
</template>
|
||||||
|
@ -19,20 +16,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import StandardCenteredPage from 'src/components/StandardCenteredPage.vue';
|
import StandardCenteredPage from 'src/components/StandardCenteredPage.vue';
|
||||||
import api from 'src/api/main';
|
import api from 'src/api/main';
|
||||||
import {ref} from 'vue';
|
import { useAuthStore } from 'stores/auth-store';
|
||||||
import {sleep} from "src/utils";
|
|
||||||
|
|
||||||
const authTestMessage = ref('');
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
async function doAuth() {
|
async function doAuth() {
|
||||||
const token = await api.auth.getToken({email: 'andrew.lalis@example.com', password: 'testpass'});
|
await api.auth.login(authStore, {
|
||||||
authTestMessage.value = 'Token: ' + token;
|
email: 'andrew.lalis@example.com',
|
||||||
await sleep(2000);
|
password: 'testpass',
|
||||||
const user = await api.auth.getMyUser(token);
|
});
|
||||||
authTestMessage.value = 'User: ' + JSON.stringify(user);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -4,11 +4,22 @@
|
||||||
<div class="col-xs-12 col-md-6 q-pt-md">
|
<div class="col-xs-12 col-md-6 q-pt-md">
|
||||||
<p>{{ $t('gymPage.homePage.overview') }}</p>
|
<p>{{ $t('gymPage.homePage.overview') }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="gym.websiteUrl">Website: <a :href="gym.websiteUrl" target="_blank">{{ gym.websiteUrl }}</a></li>
|
<li v-if="gym.websiteUrl">
|
||||||
<li>Address: <em>{{ gym.streetAddress }}</em></li>
|
Website:
|
||||||
<li>City: <em>{{ gym.cityName }}</em></li>
|
<a :href="gym.websiteUrl" target="_blank">{{ gym.websiteUrl }}</a>
|
||||||
<li>Country: <em>{{ gym.countryName }}</em></li>
|
</li>
|
||||||
<li>Registered at: <em>{{ gym.createdAt }}</em></li>
|
<li>
|
||||||
|
Address: <em>{{ gym.streetAddress }}</em>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
City: <em>{{ gym.cityName }}</em>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Country: <em>{{ gym.countryName }}</em>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Registered at: <em>{{ gym.createdAt }}</em>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
|
@ -19,7 +30,11 @@
|
||||||
<div v-if="recentSubmissions.length > 0">
|
<div v-if="recentSubmissions.length > 0">
|
||||||
<h4 class="text-center">{{ $t('gymPage.homePage.recentLifts') }}</h4>
|
<h4 class="text-center">{{ $t('gymPage.homePage.recentLifts') }}</h4>
|
||||||
<q-list>
|
<q-list>
|
||||||
<ExerciseSubmissionListItem v-for="sub in recentSubmissions" :submission="sub" :key="sub.id"/>
|
<ExerciseSubmissionListItem
|
||||||
|
v-for="sub in recentSubmissions"
|
||||||
|
:submission="sub"
|
||||||
|
:key="sub.id"
|
||||||
|
/>
|
||||||
</q-list>
|
</q-list>
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
|
@ -39,7 +54,8 @@ const recentSubmissions: Ref<Array<ExerciseSubmission>> = ref([]);
|
||||||
const gym: Ref<Gym | undefined> = ref();
|
const gym: Ref<Gym | undefined> = ref();
|
||||||
|
|
||||||
const TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
const TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
const ATTRIBUTION = '© <a href="https://www.openstreetmap.org/copyright">OSM</a>';
|
const ATTRIBUTION =
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OSM</a>';
|
||||||
const map: Ref<Map | undefined> = ref();
|
const map: Ref<Map | undefined> = ref();
|
||||||
const mapContainer = ref();
|
const mapContainer = ref();
|
||||||
|
|
||||||
|
@ -55,13 +71,18 @@ function initMap() {
|
||||||
const g: Gym = gym.value;
|
const g: Gym = gym.value;
|
||||||
console.log(mapContainer);
|
console.log(mapContainer);
|
||||||
|
|
||||||
const tiles = new TileLayer(TILE_URL, { attribution: ATTRIBUTION, maxZoom: 19 });
|
const tiles = new TileLayer(TILE_URL, {
|
||||||
|
attribution: ATTRIBUTION,
|
||||||
|
maxZoom: 19,
|
||||||
|
});
|
||||||
const marker = new Marker([g.location.latitude, g.location.longitude], {
|
const marker = new Marker([g.location.latitude, g.location.longitude], {
|
||||||
title: g.displayName,
|
title: g.displayName,
|
||||||
alt: g.displayName
|
alt: g.displayName,
|
||||||
});
|
});
|
||||||
map.value = new Map(mapContainer.value, {})
|
map.value = new Map(mapContainer.value, {}).setView(
|
||||||
.setView([g.location.latitude, g.location.longitude], 16);
|
[g.location.latitude, g.location.longitude],
|
||||||
|
16
|
||||||
|
);
|
||||||
|
|
||||||
tiles.addTo(map.value);
|
tiles.addTo(map.value);
|
||||||
marker.addTo(map.value);
|
marker.addTo(map.value);
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<q-page>
|
<q-page>
|
||||||
<div class="q-ma-md row justify-end q-gutter-sm">
|
<div class="q-ma-md row justify-end q-gutter-sm">
|
||||||
<q-spinner
|
<q-spinner color="primary" size="3em" v-if="loadingIndicatorActive" />
|
||||||
color="primary"
|
|
||||||
size="3em"
|
|
||||||
v-if="loadingIndicatorActive"
|
|
||||||
/>
|
|
||||||
<q-select
|
<q-select
|
||||||
v-model="selectedExercise"
|
v-model="selectedExercise"
|
||||||
:options="exerciseOptions"
|
:options="exerciseOptions"
|
||||||
|
@ -22,7 +18,11 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<q-list>
|
<q-list>
|
||||||
<ExerciseSubmissionListItem v-for="sub in submissions" :submission="sub" :key="sub.id"/>
|
<ExerciseSubmissionListItem
|
||||||
|
v-for="sub in submissions"
|
||||||
|
:submission="sub"
|
||||||
|
:key="sub.id"
|
||||||
|
/>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,10 +43,10 @@ const gym: Ref<Gym | undefined> = ref();
|
||||||
const exercises: Ref<Array<Exercise>> = ref([]);
|
const exercises: Ref<Array<Exercise>> = ref([]);
|
||||||
|
|
||||||
const exerciseOptions = computed(() => {
|
const exerciseOptions = computed(() => {
|
||||||
let options = exercises.value.map(exercise => {
|
let options = exercises.value.map((exercise) => {
|
||||||
return {
|
return {
|
||||||
value: exercise.shortName,
|
value: exercise.shortName,
|
||||||
label: exercise.displayName
|
label: exercise.displayName,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
options.push({ value: '', label: 'Any' });
|
options.push({ value: '', label: 'Any' });
|
||||||
|
@ -61,7 +61,9 @@ const timeframeOptions = [
|
||||||
{ value: LeaderboardTimeframe.YEAR, label: 'Year' },
|
{ value: LeaderboardTimeframe.YEAR, label: 'Year' },
|
||||||
{ value: LeaderboardTimeframe.ALL, label: 'All' },
|
{ value: LeaderboardTimeframe.ALL, label: 'All' },
|
||||||
];
|
];
|
||||||
const selectedTimeframe: Ref<LeaderboardTimeframe> = ref(LeaderboardTimeframe.DAY);
|
const selectedTimeframe: Ref<LeaderboardTimeframe> = ref(
|
||||||
|
LeaderboardTimeframe.DAY
|
||||||
|
);
|
||||||
|
|
||||||
const loadingIndicatorActive = ref(false);
|
const loadingIndicatorActive = ref(false);
|
||||||
|
|
||||||
|
@ -79,7 +81,7 @@ async function doSearch() {
|
||||||
submissions.value = await api.leaderboards.getLeaderboard({
|
submissions.value = await api.leaderboards.getLeaderboard({
|
||||||
timeframe: selectedTimeframe.value,
|
timeframe: selectedTimeframe.value,
|
||||||
gyms: [gym.value],
|
gyms: [gym.value],
|
||||||
exerciseShortName: selectedExercise.value
|
exerciseShortName: selectedExercise.value,
|
||||||
});
|
});
|
||||||
loadingIndicatorActive.value = false;
|
loadingIndicatorActive.value = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,9 @@ onMounted(async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function submitButtonEnabled() {
|
function submitButtonEnabled() {
|
||||||
return selectedVideoFile.value !== undefined && !submitting.value && validateForm();
|
return (
|
||||||
|
selectedVideoFile.value !== undefined && !submitting.value && validateForm()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateForm() {
|
function validateForm() {
|
||||||
|
@ -176,7 +178,6 @@ async function onSubmitted() {
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/**
|
||||||
|
* This store keeps track of the authentication state of the web app, which
|
||||||
|
* is just keeping the current user and their token.
|
||||||
|
*
|
||||||
|
* See src/api/main/auth.ts for mutators of this store.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { User } from 'src/api/main/auth';
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
user: User | null;
|
||||||
|
token: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('authStore', {
|
||||||
|
state: (): AuthState => {
|
||||||
|
return { user: null, token: null };
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
loggedIn: (state) => state.user !== null && state.token !== null,
|
||||||
|
axiosConfig(state) {
|
||||||
|
if (this.token !== null) {
|
||||||
|
return {
|
||||||
|
headers: { Authorization: 'Bearer ' + state.token },
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AuthStoreType = ReturnType<typeof useAuthStore>;
|
Loading…
Reference in New Issue