Client - add/delete an OAuth app (WIP)

This commit is contained in:
Sam
2022-05-28 17:14:52 +02:00
parent 25decef696
commit 7e45923b25
15 changed files with 479 additions and 11 deletions

View File

@ -0,0 +1,170 @@
<template>
<div id="new-oauth2-app">
<p id="new-oauth2-title">{{ $t('oauth2.ADD_A_NEW_APP') }}</p>
<div id="apps-form">
<form @submit.prevent="createApp">
<div class="form-items">
<div class="form-item">
<label for="app-name">{{ $t('oauth2.APP.NAME') }}*</label>
<input
id="app-name"
type="text"
required
v-model="appForm.client_name"
/>
</div>
<div class="form-item">
<label for="app-description">{{
$t('oauth2.APP.DESCRIPTION')
}}</label>
<CustomTextArea
name="app-description"
:charLimit="200"
:input="appForm.description"
@updateValue="updateDescription"
/>
</div>
<div class="form-item">
<label for="app-url">{{ $t('oauth2.APP.URL') }}*</label>
<input
id="app-url"
type="text"
required
v-model="appForm.client_uri"
/>
</div>
<div class="form-item">
<label for="app-redirect-uri"
>{{ $t('oauth2.APP.REDIRECT_URL') }}*</label
>
<input
id="app-redirect-uri"
type="text"
required
v-model="appForm.redirect_uri"
/>
</div>
<div class="form-item-scope">
<div class="form-item-scope-label">
{{ $t('oauth2.APP.SCOPE.LABEL') }}*
</div>
<div class="form-item-scope-checkboxes">
<label>
<input
type="checkbox"
:checked="appForm.read"
@change="appForm.read = !appForm.read"
/>
{{ $t('oauth2.APP.SCOPE.READ') }}
</label>
<label>
<input
type="checkbox"
:checked="appForm.write"
@change="appForm.write = !appForm.write"
/>
{{ $t('oauth2.APP.SCOPE.WRITE') }}
</label>
</div>
</div>
</div>
<div class="form-buttons">
<button
class="confirm"
type="submit"
:disabled="!appForm.read && !appForm.write"
>
{{ $t('buttons.SUBMIT') }}
</button>
<button
class="cancel"
@click.prevent="() => $router.push('/profile/apps')"
>
{{ $t('buttons.CANCEL') }}
</button>
</div>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { OAUTH2_STORE } from '@/store/constants'
import { IOAuth2ClientPayload } from '@/types/oauth'
import { useStore } from '@/use/useStore'
const store = useStore()
const appForm = reactive({
client_name: '',
client_uri: '',
client_description: '',
redirect_uri: '',
read: true,
write: false,
})
function createApp() {
const payload: IOAuth2ClientPayload = {
client_name: appForm.client_name,
client_description: appForm.client_description,
client_uri: appForm.client_uri,
redirect_uris: [appForm.redirect_uri],
scope: `${appForm.read ? 'read' : ''} ${appForm.write ? 'write' : ''}`,
}
store.dispatch(OAUTH2_STORE.ACTIONS.CREATE_CLIENT, payload)
}
function updateDescription(value: string) {
appForm.client_description = value
}
</script>
<style scoped lang="scss">
@import '~@/scss/vars.scss';
#new-oauth2-app {
#new-oauth2-title {
font-size: 1.05em;
font-weight: bold;
padding: 0 $default-padding;
}
#apps-form {
.form-items {
display: flex;
flex-direction: column;
input {
height: 20px;
}
.form-item-scope {
padding: $default-padding;
.form-item-scope-label {
font-weight: bold;
}
.form-item-scope-checkboxes {
display: flex;
gap: $default-padding;
}
}
.form-item {
display: flex;
flex-direction: column;
padding: $default-padding;
}
}
.form-buttons {
display: flex;
justify-content: flex-end;
button {
margin: $default-padding * 0.5;
}
}
}
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<div id="oauth2-app" class="description-list">
<Modal
v-if="displayModal"
:title="$t('common.CONFIRMATION')"
:message="$t('oauth2.APP_DELETION_CONFIRMATION')"
@confirmAction="deleteClient(client.id)"
@cancelAction="updateDisplayModal(false)"
/>
<div v-if="client && client.client_id">
<dl>
<dt>{{ $t('oauth2.APP.CLIENT_ID') }}:</dt>
<dd>{{ client.client_id }}</dd>
<dt>{{ capitalize($t('oauth2.APP.ISSUE_AT')) }}:</dt>
<dd>
{{
format(
getDateWithTZ(client.issued_at, authUser.timezone),
'dd/MM/yyyy HH:mm'
)
}}
</dd>
<dt>{{ $t('oauth2.APP.NAME') }}:</dt>
<dd>{{ client.name }}</dd>
<dt>{{ $t('oauth2.APP.DESCRIPTION') }}:</dt>
<dd :class="{ 'no-description': !client.client_description }">
{{
client.client_description
? client.client_description
: $t('oauth2.NO_DESCRIPTION')
}}
</dd>
<dt>{{ $t('oauth2.APP.URL') }}:</dt>
<dd>{{ client.website }}</dd>
<dt>{{ $t('oauth2.APP.REDIRECT_URL') }}:</dt>
<dd>
{{ client.redirect_uris.length > 0 ? client.redirect_uris[0] : '' }}
</dd>
<dt>{{ $t('oauth2.APP.SCOPE.LABEL') }}:</dt>
<dd>{{ client.scope }}</dd>
</dl>
<div class="app-buttons">
<button class="danger" @click="updateDisplayModal(true)">
{{ $t('oauth2.DELETE_APP') }}
</button>
<button @click="$router.push('/profile/apps')">
{{ $t('buttons.BACK') }}
</button>
</div>
</div>
<div v-else>
<p class="no-app">{{ $t('oauth2.NO_APP') }}</p>
<button @click="$router.push('/profile/apps')">
{{ $t('buttons.BACK') }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { format } from 'date-fns'
import {
ComputedRef,
Ref,
capitalize,
computed,
onBeforeMount,
toRefs,
ref,
onUnmounted,
} from 'vue'
import { useRoute } from 'vue-router'
import { OAUTH2_STORE, ROOT_STORE } from '@/store/constants'
import { IOAuth2Client } from '@/types/oauth'
import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { getDateWithTZ } from '@/utils/dates'
interface Props {
authUser: IAuthUserProfile
}
const props = defineProps<Props>()
const route = useRoute()
const store = useStore()
const { authUser } = toRefs(props)
const client: ComputedRef<IOAuth2Client> = computed(
() => store.getters[OAUTH2_STORE.GETTERS.CLIENT]
)
let displayModal: Ref<boolean> = ref(false)
onBeforeMount(() => {
loadClient()
})
function loadClient() {
if (route.params.clientId && typeof route.params.clientId === 'string') {
store.dispatch(OAUTH2_STORE.ACTIONS.GET_CLIENT, route.params.clientId)
}
}
function deleteClient(clientId: number) {
store.dispatch(OAUTH2_STORE.ACTIONS.DELETE_CLIENT, clientId)
}
function updateDisplayModal(value: boolean) {
displayModal.value = value
}
onUnmounted(() => {
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
store.commit(OAUTH2_STORE.MUTATIONS.EMPTY_CLIENT)
})
</script>
<style scoped lang="scss">
@import '~@/scss/vars.scss';
#oauth2-app {
.app-buttons {
display: flex;
flex-wrap: wrap;
gap: $default-padding;
}
.no-description {
font-style: italic;
}
.no-app {
font-style: italic;
padding: $default-padding 0;
}
}
</style>

View File

@ -3,7 +3,9 @@
<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 }}
<router-link :to="{ name: 'UserApp', params: { clientId: client.id } }">
{{ client.name }}
</router-link>
<span class="app-issued-at">
{{ $t('oauth2.APP.ISSUE_AT') }}
{{
@ -17,7 +19,12 @@
</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 class="app-list-buttons">
<button @click="$router.push('/profile/apps/new')">
{{ $t('oauth2.NEW_APP') }}
</button>
<button @click="$router.push('/')">{{ $t('common.HOME') }}</button>
</div>
</div>
</template>
@ -92,11 +99,16 @@
.app-issued-at {
font-size: 0.85em;
font-style: italic;
padding-left: $default-padding;
}
.apps-list {
font-size: 1.05em;
font-weight: bold;
}
.app-list-buttons {
display: flex;
gap: $default-padding;
}
.no-apps {
font-style: italic;
}

View File

@ -5,14 +5,23 @@
</template>
<script setup lang="ts">
import { toRefs } from 'vue'
import { onUnmounted, toRefs } from 'vue'
import { OAUTH2_STORE, ROOT_STORE } from '@/store/constants'
import { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
interface Props {
user: IAuthUserProfile
}
const props = defineProps<Props>()
const store = useStore()
const { user } = toRefs(props)
onUnmounted(() => {
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
store.commit(OAUTH2_STORE.MUTATIONS.SET_CLIENTS, [])
})
</script>

View File

@ -7,7 +7,7 @@
type="radio"
:id="tab"
:name="tab"
:checked="selectedTab === tab"
:checked="selectedTab.split('/')[0] === tab"
:disabled="disabled"
@input="$router.push(getPath(tab))"
/>