Added status controllers, and start of user settings page.
This commit is contained in:
parent
aa843227d2
commit
0564cd5789
|
@ -6,23 +6,37 @@ const api = axios.create({
|
|||
});
|
||||
|
||||
/**
|
||||
* Searches for gyms using the given query, and eventually returns results.
|
||||
* @param query The query to use.
|
||||
* The search API class.
|
||||
*/
|
||||
export async function searchGyms(
|
||||
query: string
|
||||
): Promise<Array<GymSearchResult>> {
|
||||
const response = await api.get('/search/gyms?q=' + query);
|
||||
return response.data;
|
||||
class SearchApi {
|
||||
/**
|
||||
* Searches for gyms using the given query, and eventually returns results.
|
||||
* @param query The query to use.
|
||||
*/
|
||||
public async searchGyms(query: string): Promise<Array<GymSearchResult>> {
|
||||
const response = await api.get(`/search/gyms?q=${query}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for users using the given query, and eventually returns results.
|
||||
* Note that only users whose accounts are not private will be included in
|
||||
* search results.
|
||||
* @param query The query to use.
|
||||
*/
|
||||
public async searchUsers(query: string): Promise<Array<UserSearchResult>> {
|
||||
const response = await api.get(`/search/users?q=${query}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getStatus(): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.get(`/status`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for users using the given query, and eventually returns results.
|
||||
* Note that only users whose accounts are not private will be included in
|
||||
* search results.
|
||||
* @param query The query to use.
|
||||
*/
|
||||
export async function searchUsers(query: string): Promise<Array<UserSearchResult>> {
|
||||
const response = await api.get('/search/users?q=' + query);
|
||||
return response.data;
|
||||
}
|
||||
export default new SearchApi();
|
||||
|
|
|
@ -3,14 +3,19 @@
|
|||
<q-btn-dropdown
|
||||
color="primary"
|
||||
:label="authStore.user?.name"
|
||||
v-if="authStore.loggedIn"
|
||||
v-if="authStore.loggedIn && authStore.user"
|
||||
no-caps
|
||||
icon="person"
|
||||
>
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup :to="'/users/' + authStore.user?.id">
|
||||
<q-item clickable v-close-popup :to="getUserRoute(authStore.user)">
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('accountMenuItem.myAccount') }}</q-item-label>
|
||||
<q-item-label>{{ $t('accountMenuItem.profile') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup :to="getUserRoute(authStore.user) + '/settings'">
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('accountMenuItem.settings') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup @click="api.auth.logout(authStore)">
|
||||
|
@ -35,6 +40,7 @@
|
|||
import { useAuthStore } from 'stores/auth-store';
|
||||
import api from 'src/api/main';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getUserRoute } from 'src/router/user-routing';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const route = useRoute();
|
||||
|
|
|
@ -17,6 +17,7 @@ export default {
|
|||
password: 'Password',
|
||||
logIn: 'Log in',
|
||||
createAccount: 'Create an account',
|
||||
authFailed: 'Invalid credentials.'
|
||||
},
|
||||
indexPage: {
|
||||
searchHint: 'Search for a Gym',
|
||||
|
@ -45,9 +46,16 @@ export default {
|
|||
description: 'We couldn\'t find the user you\'re looking for.'
|
||||
}
|
||||
},
|
||||
userSettingsPage: {
|
||||
title: 'Account Settings'
|
||||
},
|
||||
accountMenuItem: {
|
||||
logIn: 'Login',
|
||||
myAccount: 'My Account',
|
||||
profile: 'Profile',
|
||||
settings: 'Settings',
|
||||
logOut: 'Log out',
|
||||
},
|
||||
generalErrors: {
|
||||
apiError: 'An API error occurred. Please try again later.'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,3 +7,23 @@ export default {
|
|||
'nl-NL': nlNL,
|
||||
de: de,
|
||||
};
|
||||
|
||||
export const supportedLocales = [
|
||||
{ value: 'en-US', label: 'English' },
|
||||
{ value: 'nl-NL', label: 'Nederlands' },
|
||||
{ value: 'de', label: 'Deutsch' },
|
||||
];
|
||||
|
||||
/**
|
||||
* Tries to find a locale with the given code, or defaults to the base locale.
|
||||
* @param code The locale code.
|
||||
* @returns The locale that was resolved.
|
||||
*/
|
||||
export function resolveLocale(code?: string) {
|
||||
for (const loc of supportedLocales) {
|
||||
if (loc.value === code) {
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
return supportedLocales[0];
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
>
|
||||
</q-toolbar-title>
|
||||
<AccountMenuItem />
|
||||
<LocaleSelect />
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
|
@ -41,7 +40,6 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import LocaleSelect from 'components/LocaleSelect.vue';
|
||||
import AccountMenuItem from 'components/AccountMenuItem.vue';
|
||||
|
||||
const leftDrawerOpen = ref(false);
|
||||
|
|
|
@ -30,7 +30,8 @@ import { useRoute, useRouter } from 'vue-router';
|
|||
import GymSearchResultListItem from 'components/GymSearchResultListItem.vue';
|
||||
import StandardCenteredPage from 'src/components/StandardCenteredPage.vue';
|
||||
import { GymSearchResult } from 'src/api/search/models';
|
||||
import { searchGyms } from 'src/api/search';
|
||||
import searchApi from 'src/api/search';
|
||||
import { sleep } from 'src/utils';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
@ -47,6 +48,7 @@ onMounted(async () => {
|
|||
if (route.query.search_query) {
|
||||
searchQuery.value = route.query.search_query as string;
|
||||
searchBarLoadingState.value = true;
|
||||
await sleep(500);
|
||||
await doSearch();
|
||||
}
|
||||
});
|
||||
|
@ -74,7 +76,7 @@ async function doSearch() {
|
|||
}
|
||||
await router.push({ path: '/', query: query });
|
||||
try {
|
||||
searchResults.value = await searchGyms(searchQueryText);
|
||||
searchResults.value = await searchApi.searchGyms(searchQueryText);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
|
|
@ -2,8 +2,13 @@
|
|||
<q-page>
|
||||
<StandardCenteredPage v-if="user">
|
||||
<h3>{{ user?.name }}</h3>
|
||||
|
||||
<p>{{ user?.email }}</p>
|
||||
<p v-if="isOwnUser">This is your account!</p>
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
</StandardCenteredPage>
|
||||
<StandardCenteredPage v-if="userNotFound">
|
||||
<h3>{{ $t('userPage.notFound.title') }}</h3>
|
||||
|
@ -14,11 +19,12 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import StandardCenteredPage from 'components/StandardCenteredPage.vue';
|
||||
import {onMounted, ref, Ref} from 'vue';
|
||||
import {computed, onMounted, ref, Ref} from 'vue';
|
||||
import {User} from 'src/api/main/auth';
|
||||
import api from 'src/api/main';
|
||||
import {useRoute} from 'vue-router';
|
||||
import {useAuthStore} from 'stores/auth-store';
|
||||
import { getUserRoute } from 'src/router/user-routing';
|
||||
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
|
@ -48,7 +54,7 @@ onMounted(async () => {
|
|||
userNotFound.value = true;
|
||||
}
|
||||
}
|
||||
isOwnUser.value = authStore.loggedIn && user.value.id === authStore.user?.id;
|
||||
isOwnUser.value = authStore.loggedIn && user.value?.id === authStore.user?.id;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -59,10 +59,15 @@ import { ref } from 'vue';
|
|||
import api from 'src/api/main';
|
||||
import { useAuthStore } from 'stores/auth-store';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { resolveLocale } from 'src/i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const i18n = useI18n({ useScope: 'global' });
|
||||
const quasar = useQuasar();
|
||||
|
||||
const loginModel = ref({
|
||||
email: '',
|
||||
|
@ -70,15 +75,40 @@ const loginModel = ref({
|
|||
});
|
||||
const passwordVisible = ref(false);
|
||||
|
||||
/**
|
||||
* The main login function. It attempts to log in the user, and gracefully
|
||||
* handles failures.
|
||||
*
|
||||
* Upon successful login, we set the app's locale to the user's preferred
|
||||
* locale, and then redirect to the pre-configured next URL (if there is one).
|
||||
*/
|
||||
async function tryLogin() {
|
||||
try {
|
||||
await api.auth.login(authStore, loginModel.value);
|
||||
|
||||
// Set the locale to the user's preferred locale.
|
||||
i18n.locale.value = resolveLocale(authStore.user?.preferences?.locale);
|
||||
|
||||
// Redirect back to whatever was set as the next URL.
|
||||
const dest = route.query.next
|
||||
? decodeURIComponent(route.query.next as string)
|
||||
: '/';
|
||||
await router.push(dest);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} catch (error: any) {
|
||||
if (error.response && error.response.status === 401) {
|
||||
quasar.notify({
|
||||
message: i18n.t('loginPage.authFailed'),
|
||||
type: 'warning',
|
||||
position: 'top',
|
||||
});
|
||||
} else {
|
||||
quasar.notify({
|
||||
message: i18n.t('generalErrors.apiError'),
|
||||
type: 'negative',
|
||||
position: 'top',
|
||||
});
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<StandardCenteredPage>
|
||||
<h3>{{ $t('userSettingsPage.title') }}</h3>
|
||||
</StandardCenteredPage>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import StandardCenteredPage from 'components/StandardCenteredPage.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -13,6 +13,7 @@ import RegistrationSuccessPage from 'pages/auth/RegistrationSuccessPage.vue';
|
|||
import ActivationPage from 'pages/auth/ActivationPage.vue';
|
||||
import SubmissionPage from 'pages/SubmissionPage.vue';
|
||||
import UserPage from 'pages/UserPage.vue';
|
||||
import UserSettingsPage from 'pages/auth/UserSettingsPage.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
// Auth-related pages, which live outside the main layout.
|
||||
|
@ -28,7 +29,15 @@ const routes: RouteRecordRaw[] = [
|
|||
children: [
|
||||
{ path: '', component: IndexPage },
|
||||
{ path: 'testing', component: TestingPage },
|
||||
{ path: 'users/:userId', component: UserPage },
|
||||
{
|
||||
path: 'users/:userId',
|
||||
children: [
|
||||
{ path: '', component: UserPage },
|
||||
{ path: 'settings', component: UserSettingsPage }
|
||||
]
|
||||
},
|
||||
// { path: 'users/:userId', component: UserPage },
|
||||
// { path: 'users/:userId/settings', component: UserSettingsPage },
|
||||
{
|
||||
path: 'gyms/:countryCode/:cityShortName/:gymShortName',
|
||||
component: GymPage,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { User } from 'src/api/main/auth';
|
||||
|
||||
export function getUserRoute(user: User) {
|
||||
return `/users/${user.id}`;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package nl.andrewlalis.gymboardcdn.api;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class StatusController {
|
||||
@GetMapping(path = "/status")
|
||||
public ResponseEntity<?> getStatus() {
|
||||
return ResponseEntity.ok(Map.of("online", true));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package nl.andrewlalis.gymboardsearch;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
public class StatusController {
|
||||
@GetMapping(path = "/status")
|
||||
public ResponseEntity<?> getStatus() {
|
||||
return ResponseEntity.ok(Map.of("online", true));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue