Client - init OAuth Applications list

This commit is contained in:
Sam 2022-05-28 16:16:36 +02:00
parent 489710c29d
commit 25decef696
25 changed files with 358 additions and 3 deletions

View File

@ -50,13 +50,14 @@
import { toRefs } from 'vue' import { toRefs } from 'vue'
import { IPagination, TPaginationPayload } from '@/types/api' import { IPagination, TPaginationPayload } from '@/types/api'
import { IOauth2ClientsPayload } from '@/types/oauth'
import { TWorkoutsPayload } from '@/types/workouts' import { TWorkoutsPayload } from '@/types/workouts'
import { rangePagination } from '@/utils/api' import { rangePagination } from '@/utils/api'
interface Props { interface Props {
pagination: IPagination pagination: IPagination
path: string path: string
query: TWorkoutsPayload | TPaginationPayload query: TWorkoutsPayload | TPaginationPayload | IOauth2ClientsPayload
} }
const props = defineProps<Props>() const props = defineProps<Props>()

View File

@ -22,7 +22,7 @@
const props = defineProps<Props>() const props = defineProps<Props>()
const { user, tab } = toRefs(props) const { user, tab } = toRefs(props)
const tabs = ['PROFILE', 'PREFERENCES', 'SPORTS'] const tabs = ['PROFILE', 'PREFERENCES', 'SPORTS', 'APPS']
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -0,0 +1,104 @@
<template>
<div id="oauth2-apps-list">
<p class="apps-list">{{ $t('oauth2.APPS_LIST') }}</p>
<ul v-if="clients.length > 0">
<li v-for="client in clients" :key="client.client_id">
{{ client.name }}
<span class="app-issued-at">
{{ $t('oauth2.APP.ISSUE_AT') }}
{{
format(
getDateWithTZ(client.issued_at, authUser.timezone),
'dd/MM/yyyy HH:mm'
)
}}
</span>
</li>
</ul>
<div class="no-apps" v-else>{{ $t('oauth2.NO_APPS') }}</div>
<Pagination :pagination="pagination" path="/profile/apps" :query="query" />
<button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
</div>
</template>
<script setup lang="ts">
import { format } from 'date-fns'
import { ComputedRef, computed, onBeforeMount, toRefs, watch } from 'vue'
import { LocationQuery, useRoute } from 'vue-router'
import Pagination from '@/components/Common/Pagination.vue'
import { OAUTH2_STORE } from '@/store/constants'
import { IPagination } from '@/types/api'
import { IOAuth2Client, IOauth2ClientsPayload } from '@/types/oauth'
import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { defaultPage, getNumberQueryValue } from '@/utils/api'
import { getDateWithTZ } from '@/utils/dates'
interface Props {
authUser: IAuthUserProfile
}
const props = defineProps<Props>()
const store = useStore()
const route = useRoute()
const { authUser } = toRefs(props)
const clients: ComputedRef<IOAuth2Client[]> = computed(
() => store.getters[OAUTH2_STORE.GETTERS.CLIENTS]
)
const pagination: ComputedRef<IPagination> = computed(
() => store.getters[OAUTH2_STORE.GETTERS.CLIENTS_PAGINATION]
)
let query: IOauth2ClientsPayload = getClientsQuery(route.query)
onBeforeMount(() => {
loadClients(query)
})
function getClientsQuery(newQuery: LocationQuery): IOauth2ClientsPayload {
let clientsQuery: IOauth2ClientsPayload = {}
if (newQuery.page) {
clientsQuery.page = getNumberQueryValue(newQuery.page, defaultPage)
}
return clientsQuery
}
function loadClients(payload: IOauth2ClientsPayload) {
store.dispatch(OAUTH2_STORE.ACTIONS.GET_CLIENTS, payload)
}
watch(
() => route.query,
async (newQuery) => {
query = getClientsQuery(newQuery)
loadClients(query)
}
)
</script>
<style scoped lang="scss">
@import '~@/scss/vars.scss';
#oauth2-apps-list {
ul {
list-style: square;
li {
padding-bottom: $default-padding;
}
}
.app-issued-at {
font-size: 0.85em;
font-style: italic;
}
.apps-list {
font-size: 1.05em;
font-weight: bold;
}
.no-apps {
font-style: italic;
}
}
</style>

View File

@ -0,0 +1,18 @@
<template>
<div id="oauth2-apps">
<router-view :authUser="user"></router-view>
</div>
</template>
<script setup lang="ts">
import { toRefs } from 'vue'
import { IAuthUserProfile } from '@/types/user'
interface Props {
user: IAuthUserProfile
}
const props = defineProps<Props>()
const { user } = toRefs(props)
</script>

View File

@ -38,6 +38,7 @@
case 'ACCOUNT': case 'ACCOUNT':
case 'PICTURE': case 'PICTURE':
return `/profile/edit/${tab.toLocaleLowerCase()}` return `/profile/edit/${tab.toLocaleLowerCase()}`
case 'APPS':
case 'PREFERENCES': case 'PREFERENCES':
case 'SPORTS': case 'SPORTS':
return `/profile${ return `/profile${

View File

@ -5,6 +5,7 @@ import ButtonsTranslations from './buttons.json'
import CommonTranslations from './common.json' import CommonTranslations from './common.json'
import DashboardTranslations from './dashboard.json' import DashboardTranslations from './dashboard.json'
import ErrorTranslations from './error.json' import ErrorTranslations from './error.json'
import OAuth2Translations from './oauth2.json'
import SportsTranslations from './sports.json' import SportsTranslations from './sports.json'
import StatisticsTranslations from './statistics.json' import StatisticsTranslations from './statistics.json'
import UserTranslations from './user.json' import UserTranslations from './user.json'
@ -18,6 +19,7 @@ export default {
common: CommonTranslations, common: CommonTranslations,
dashboard: DashboardTranslations, dashboard: DashboardTranslations,
error: ErrorTranslations, error: ErrorTranslations,
oauth2: OAuth2Translations,
sports: SportsTranslations, sports: SportsTranslations,
statistics: StatisticsTranslations, statistics: StatisticsTranslations,
user: UserTranslations, user: UserTranslations,

View File

@ -0,0 +1,7 @@
{
"APP":{
"ISSUE_AT": "issued at"
},
"APPS_LIST": "OAuth2 applications",
"NO_APPS": "No applications"
}

View File

@ -72,6 +72,7 @@
"SUNDAY": "Sunday", "SUNDAY": "Sunday",
"TABS": { "TABS": {
"ACCOUNT": "account", "ACCOUNT": "account",
"APPS": "apps",
"PICTURE": "picture", "PICTURE": "picture",
"PREFERENCES": "preferences", "PREFERENCES": "preferences",
"PROFILE": "profile", "PROFILE": "profile",

View File

@ -5,6 +5,7 @@ import ButtonsTranslations from './buttons.json'
import CommonTranslations from './common.json' import CommonTranslations from './common.json'
import DashboardTranslations from './dashboard.json' import DashboardTranslations from './dashboard.json'
import ErrorTranslations from './error.json' import ErrorTranslations from './error.json'
import OAuth2Translations from './oauth2.json'
import SportsTranslations from './sports.json' import SportsTranslations from './sports.json'
import StatisticsTranslations from './statistics.json' import StatisticsTranslations from './statistics.json'
import UserTranslations from './user.json' import UserTranslations from './user.json'
@ -18,6 +19,7 @@ export default {
common: CommonTranslations, common: CommonTranslations,
dashboard: DashboardTranslations, dashboard: DashboardTranslations,
error: ErrorTranslations, error: ErrorTranslations,
oauth2: OAuth2Translations,
sports: SportsTranslations, sports: SportsTranslations,
statistics: StatisticsTranslations, statistics: StatisticsTranslations,
user: UserTranslations, user: UserTranslations,

View File

@ -0,0 +1,7 @@
{
"APP":{
"ISSUE_AT": "créée le"
},
"APPS_LIST": "Applications OAuth2",
"NO_APPS": "pas de applications"
}

View File

@ -72,6 +72,7 @@
"SUNDAY": "Dimanche", "SUNDAY": "Dimanche",
"TABS": { "TABS": {
"ACCOUNT": "compte", "ACCOUNT": "compte",
"APPS": "apps",
"PICTURE": "image", "PICTURE": "image",
"PREFERENCES": "préférences", "PREFERENCES": "préférences",
"PROFILE": "profil", "PROFILE": "profil",

View File

@ -12,6 +12,8 @@ import UserAccountEdition from '@/components/User/ProfileEdition/UserAccountEdit
import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue' import UserInfosEdition from '@/components/User/ProfileEdition/UserInfosEdition.vue'
import UserPictureEdition from '@/components/User/ProfileEdition/UserPictureEdition.vue' import UserPictureEdition from '@/components/User/ProfileEdition/UserPictureEdition.vue'
import UserPreferencesEdition from '@/components/User/ProfileEdition/UserPreferencesEdition.vue' import UserPreferencesEdition from '@/components/User/ProfileEdition/UserPreferencesEdition.vue'
import UserApps from '@/components/User/UserApps/index.vue'
import UserAppsList from '@/components/User/UserApps/UserAppsList.vue'
import UserSportPreferences from '@/components/User/UserSportPreferences.vue' import UserSportPreferences from '@/components/User/UserSportPreferences.vue'
import store from '@/store' import store from '@/store'
import { AUTH_USER_STORE } from '@/store/constants' import { AUTH_USER_STORE } from '@/store/constants'
@ -144,6 +146,18 @@ const routes: Array<RouteRecordRaw> = [
component: UserSportPreferences, component: UserSportPreferences,
props: { isEdition: false }, props: { isEdition: false },
}, },
{
path: 'apps',
name: 'UserApps',
component: UserApps,
children: [
{
path: '',
name: 'UserAppsList',
component: UserAppsList,
},
],
},
], ],
}, },
{ {

View File

@ -67,7 +67,7 @@ button {
transform: translateY(2px); transform: translateY(2px);
} }
&:disabled { &:disabled, &.confirm:disabled {
background: var(--disabled-background-color); background: var(--disabled-background-color);
border-color: var(--disabled-color); border-color: var(--disabled-color);
color: var(--disabled-color); color: var(--disabled-color);

View File

@ -3,6 +3,11 @@ import {
AuthUserGetters, AuthUserGetters,
AuthUserMutations, AuthUserMutations,
} from '@/store/modules/authUser/enums' } from '@/store/modules/authUser/enums'
import {
OAuth2Actions,
OAuth2Getters,
OAuth2Mutations,
} from '@/store/modules/oauth2/enums'
import { import {
RootActions, RootActions,
RootGetters, RootGetters,
@ -52,6 +57,11 @@ export const AUTH_USER_STORE = {
GETTERS: AuthUserGetters, GETTERS: AuthUserGetters,
MUTATIONS: AuthUserMutations, MUTATIONS: AuthUserMutations,
} }
export const OAUTH2_STORE = {
ACTIONS: OAuth2Actions,
GETTERS: OAuth2Getters,
MUTATIONS: OAuth2Mutations,
}
export const USERS_STORE = { export const USERS_STORE = {
ACTIONS: UsersActions, ACTIONS: UsersActions,

View File

@ -0,0 +1,36 @@
import { ActionContext, ActionTree } from 'vuex'
import authApi from '@/api/authApi'
import { OAUTH2_STORE, ROOT_STORE } from '@/store/constants'
import { IOAuth2Actions, IOAuth2State } from '@/store/modules/oauth2/types'
import { IRootState } from '@/store/modules/root/types'
import { IOauth2ClientsPayload } from '@/types/oauth'
import { handleError } from '@/utils'
export const actions: ActionTree<IOAuth2State, IRootState> & IOAuth2Actions = {
[OAUTH2_STORE.ACTIONS.GET_CLIENTS](
context: ActionContext<IOAuth2State, IRootState>,
payload: IOauth2ClientsPayload
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
authApi
.get('oauth/apps', {
params: payload,
})
.then((res) => {
if (res.data.status === 'success') {
context.commit(
OAUTH2_STORE.MUTATIONS.SET_CLIENTS,
res.data.data.clients
)
context.commit(
OAUTH2_STORE.MUTATIONS.SET_CLIENTS_PAGINATION,
res.data.pagination
)
} else {
handleError(context, null)
}
})
.catch((error) => handleError(context, error))
},
}

View File

@ -0,0 +1,13 @@
export enum OAuth2Actions {
GET_CLIENTS = 'GET_CLIENTS',
}
export enum OAuth2Getters {
CLIENTS = 'CLIENTS',
CLIENTS_PAGINATION = 'CLIENTS_PAGINATION',
}
export enum OAuth2Mutations {
SET_CLIENTS = 'SET_CLIENTS',
SET_CLIENTS_PAGINATION = 'SET_CLIENTS_PAGINATION',
}

View File

@ -0,0 +1,11 @@
import { GetterTree } from 'vuex'
import { OAUTH2_STORE } from '@/store/constants'
import { IOAuth2Getters, IOAuth2State } from '@/store/modules/oauth2/types'
import { IRootState } from '@/store/modules/root/types'
export const getters: GetterTree<IOAuth2State, IRootState> & IOAuth2Getters = {
[OAUTH2_STORE.GETTERS.CLIENTS]: (state: IOAuth2State) => state.clients,
[OAUTH2_STORE.GETTERS.CLIENTS_PAGINATION]: (state: IOAuth2State) =>
state.pagination,
}

View File

@ -0,0 +1,17 @@
import { Module } from 'vuex'
import { actions } from '@/store/modules/oauth2/actions'
import { getters } from '@/store/modules/oauth2/getters'
import { mutations } from '@/store/modules/oauth2/mutations'
import { oAuth2State } from '@/store/modules/oauth2/state'
import { IOAuth2State } from '@/store/modules/oauth2/types'
import { IRootState } from '@/store/modules/root/types'
const oauth2: Module<IOAuth2State, IRootState> = {
state: oAuth2State,
actions,
getters,
mutations,
}
export default oauth2

View File

@ -0,0 +1,21 @@
import { MutationTree } from 'vuex'
import { OAUTH2_STORE } from '@/store/constants'
import { IOAuth2State, TOAuth2Mutations } from '@/store/modules/oauth2/types'
import { IPagination } from '@/types/api'
import { IOAuth2Client } from '@/types/oauth'
export const mutations: MutationTree<IOAuth2State> & TOAuth2Mutations = {
[OAUTH2_STORE.MUTATIONS.SET_CLIENTS](
state: IOAuth2State,
clients: IOAuth2Client[]
) {
state.clients = clients
},
[OAUTH2_STORE.MUTATIONS.SET_CLIENTS_PAGINATION](
state: IOAuth2State,
pagination: IPagination
) {
state.pagination = pagination
},
}

View File

@ -0,0 +1,7 @@
import { IOAuth2State } from '@/store/modules/oauth2/types'
import { IPagination } from '@/types/api'
export const oAuth2State: IOAuth2State = {
clients: [],
pagination: <IPagination>{},
}

View File

@ -0,0 +1,60 @@
import {
ActionContext,
CommitOptions,
DispatchOptions,
Store as VuexStore,
} from 'vuex'
import { OAUTH2_STORE } from '@/store/constants'
import { IRootState } from '@/store/modules/root/types'
import { IPagination } from '@/types/api'
import { IOAuth2Client, IOauth2ClientsPayload } from '@/types/oauth'
export interface IOAuth2State {
clients: IOAuth2Client[]
pagination: IPagination
}
export interface IOAuth2Actions {
[OAUTH2_STORE.ACTIONS.GET_CLIENTS](
context: ActionContext<IOAuth2State, IRootState>,
payload: IOauth2ClientsPayload
): void
}
export interface IOAuth2Getters {
[OAUTH2_STORE.GETTERS.CLIENTS](state: IOAuth2State): IOAuth2Client[]
[OAUTH2_STORE.GETTERS.CLIENTS_PAGINATION](state: IOAuth2State): IPagination
}
export type TOAuth2Mutations<S = IOAuth2State> = {
[OAUTH2_STORE.MUTATIONS.SET_CLIENTS](state: S, clients: IOAuth2Client[]): void
[OAUTH2_STORE.MUTATIONS.SET_CLIENTS_PAGINATION](
state: S,
pagination: IPagination
): void
}
export type TOAuth2StoreModule<S = IOAuth2State> = Omit<
VuexStore<S>,
'commit' | 'getters' | 'dispatch'
> & {
dispatch<K extends keyof IOAuth2Actions>(
key: K,
payload?: Parameters<IOAuth2Actions[K]>[1],
options?: DispatchOptions
): ReturnType<IOAuth2Actions[K]>
} & {
getters: {
[K in keyof IOAuth2Getters]: ReturnType<IOAuth2Getters[K]>
}
} & {
commit<
K extends keyof TOAuth2Mutations,
P extends Parameters<TOAuth2Mutations[K]>[1]
>(
key: K,
payload?: P,
options?: CommitOptions
): ReturnType<TOAuth2Mutations[K]>
}

View File

@ -1,6 +1,7 @@
import { Module, ModuleTree } from 'vuex' import { Module, ModuleTree } from 'vuex'
import authUserModule from '@/store/modules/authUser' import authUserModule from '@/store/modules/authUser'
import oAuthModule from '@/store/modules/oauth2'
import { actions } from '@/store/modules/root/actions' import { actions } from '@/store/modules/root/actions'
import { getters } from '@/store/modules/root/getters' import { getters } from '@/store/modules/root/getters'
import { mutations } from '@/store/modules/root/mutations' import { mutations } from '@/store/modules/root/mutations'
@ -13,6 +14,7 @@ import workoutsModule from '@/store/modules/workouts'
const modules: ModuleTree<IRootState> = { const modules: ModuleTree<IRootState> = {
authUserModule, authUserModule,
oAuthModule,
sportsModule, sportsModule,
statsModule, statsModule,
usersModule, usersModule,

View File

@ -1,4 +1,5 @@
import { TAuthUserStoreModule } from '@/store/modules/authUser/types' import { TAuthUserStoreModule } from '@/store/modules/authUser/types'
import { TOAuth2StoreModule } from '@/store/modules/oauth2/types'
import { TRootStoreModule } from '@/store/modules/root/types' import { TRootStoreModule } from '@/store/modules/root/types'
import { TSportsStoreModule } from '@/store/modules/sports/types' import { TSportsStoreModule } from '@/store/modules/sports/types'
import { TStatisticsStoreModule } from '@/store/modules/statistics/types' import { TStatisticsStoreModule } from '@/store/modules/statistics/types'
@ -7,6 +8,7 @@ import { TWorkoutsStoreModule } from '@/store/modules/workouts/types'
type StoreModules = { type StoreModules = {
authUserModule: TAuthUserStoreModule authUserModule: TAuthUserStoreModule
oauth2Module: TOAuth2StoreModule
rootModule: TRootStoreModule rootModule: TRootStoreModule
sportsModule: TSportsStoreModule sportsModule: TSportsStoreModule
statsModule: TStatisticsStoreModule statsModule: TStatisticsStoreModule
@ -15,6 +17,7 @@ type StoreModules = {
} }
export type Store = TAuthUserStoreModule<Pick<StoreModules, 'authUserModule'>> & export type Store = TAuthUserStoreModule<Pick<StoreModules, 'authUserModule'>> &
TOAuth2StoreModule<Pick<StoreModules, 'oauth2Module'>> &
TSportsStoreModule<Pick<StoreModules, 'sportsModule'>> & TSportsStoreModule<Pick<StoreModules, 'sportsModule'>> &
TStatisticsStoreModule<Pick<StoreModules, 'statsModule'>> & TStatisticsStoreModule<Pick<StoreModules, 'statsModule'>> &
TWorkoutsStoreModule<Pick<StoreModules, 'workoutsModule'>> & TWorkoutsStoreModule<Pick<StoreModules, 'workoutsModule'>> &

View File

@ -0,0 +1,15 @@
export interface IOAuth2Client {
client_id: string
client_description: string | null
client_secret?: string
id: number
issued_at: string
name: string
redirect_uris: string[]
scope: string
website: string
}
export interface IOauth2ClientsPayload {
page?: number
}

View File

@ -3,6 +3,7 @@ import { ActionContext } from 'vuex'
import { ROOT_STORE } from '@/store/constants' import { ROOT_STORE } from '@/store/constants'
import { IAuthUserState } from '@/store/modules/authUser/types' import { IAuthUserState } from '@/store/modules/authUser/types'
import { IOAuth2State } from '@/store/modules/oauth2/types'
import { IRootState } from '@/store/modules/root/types' import { IRootState } from '@/store/modules/root/types'
import { ISportsState } from '@/store/modules/sports/types' import { ISportsState } from '@/store/modules/sports/types'
import { IStatisticsState } from '@/store/modules/statistics/types' import { IStatisticsState } from '@/store/modules/statistics/types'
@ -19,6 +20,7 @@ export const handleError = (
context: context:
| ActionContext<IRootState, IRootState> | ActionContext<IRootState, IRootState>
| ActionContext<IAuthUserState, IRootState> | ActionContext<IAuthUserState, IRootState>
| ActionContext<IOAuth2State, IRootState>
| ActionContext<IStatisticsState, IRootState> | ActionContext<IStatisticsState, IRootState>
| ActionContext<ISportsState, IRootState> | ActionContext<ISportsState, IRootState>
| ActionContext<IUsersState, IRootState> | ActionContext<IUsersState, IRootState>