Skip to content

Commit

Permalink
Implement Account Settings pages
Browse files Browse the repository at this point in the history
  • Loading branch information
lcharette committed Jan 20, 2025
1 parent 07b0c21 commit 2742816
Show file tree
Hide file tree
Showing 38 changed files with 988 additions and 124 deletions.
8 changes: 2 additions & 6 deletions packages/skeleton/app/assets/components/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,8 @@ const auth = useAuthStore()
:username="auth.user.full_name"
:avatar="auth.user.avatar"
:meta="auth.user.user_name">
<UFNavBarUserCardButton
label="Admin Panel"
@click="router.push({ name: 'admin.dashboard' })" />
<UFNavBarUserCardButton
label="My Account"
@click="router.push({ name: 'admin.dashboard' })" />
<UFNavBarUserCardButton label="Admin Panel" :to="{ name: 'admin.dashboard' }" />
<UFNavBarUserCardButton label="My Account" :to="{ name: 'account.settings' }" />
<UFNavBarUserCardButton label="Logout" @click="auth.logout()" />
</UFNavBarUserCard>
</UFNavBar>
Expand Down
3 changes: 3 additions & 0 deletions packages/sprinkle-account/app/assets/composables/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export * as Register from './register'
export { forgotPassword } from './forgotPassword'
export { resendVerification } from './resendVerification'
export { useUserProfileEditApi } from './useUserProfileEditApi'
export { useUserPasswordEditApi } from './useUserPasswordEditApi'
export { useUserEmailEditApi } from './useUserEmailEditApi'
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ref } from 'vue'
import axios from 'axios'
import { Severity } from '@userfrosting/sprinkle-core/interfaces'
import type { ApiResponse, AlertInterface } from '@userfrosting/sprinkle-core/interfaces'
import type { EmailEditRequest } from '../interfaces'

// TODO : Add validation
// 'schema://requests/account-email.yaml'

/**
* API Composable
*/
export function useUserEmailEditApi() {
const apiLoading = ref<Boolean>(false)
const apiError = ref<AlertInterface | null>(null)

async function submitEmailEdit(data: EmailEditRequest) {
apiLoading.value = true
apiError.value = null
return axios
.post<ApiResponse>('/account/settings/email', data)
.then((response) => {
return {
message: response.data.message
}
})
.catch((err) => {
apiError.value = {
...{
description: 'An error as occurred',
style: Severity.Danger,
closeBtn: true
},
...err.response.data
}

throw apiError.value
})
.finally(() => {
apiLoading.value = false
})
}

return { submitEmailEdit, apiLoading, apiError }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ref } from 'vue'
import axios from 'axios'
import { Severity } from '@userfrosting/sprinkle-core/interfaces'
import type { ApiResponse, AlertInterface } from '@userfrosting/sprinkle-core/interfaces'
import type { PasswordEditRequest } from '../interfaces'

// TODO : Add validation
// 'schema://requests/account-settings.yaml'

/**
* API Composable
*/
export function useUserPasswordEditApi() {
const apiLoading = ref<Boolean>(false)
const apiError = ref<AlertInterface | null>(null)

async function submitPasswordEdit(data: PasswordEditRequest) {
apiLoading.value = true
apiError.value = null
return axios
.post<ApiResponse>('/account/settings', data)
.then((response) => {
return {
message: response.data.message
}
})
.catch((err) => {
apiError.value = {
...{
description: 'An error as occurred',
style: Severity.Danger,
closeBtn: true
},
...err.response.data
}

throw apiError.value
})
.finally(() => {
apiLoading.value = false
})
}

return { submitPasswordEdit, apiLoading, apiError }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ref } from 'vue'
import axios from 'axios'
import { Severity } from '@userfrosting/sprinkle-core/interfaces'
import type { ApiResponse, AlertInterface } from '@userfrosting/sprinkle-core/interfaces'
import type { ProfileEditRequest } from '../interfaces'

// TODO : Add validation
// 'schema://requests/profile-settings.yaml'

/**
* API Composable
*/
export function useUserProfileEditApi() {
const apiLoading = ref<Boolean>(false)
const apiError = ref<AlertInterface | null>(null)

async function submitProfileEdit(data: ProfileEditRequest) {
apiLoading.value = true
apiError.value = null
return axios
.post<ApiResponse>('/account/settings/profile', data)
.then((response) => {
return {
message: response.data.message
}
})
.catch((err) => {
apiError.value = {
...{
description: 'An error as occurred',
style: Severity.Danger,
closeBtn: true
},
...err.response.data
}

throw apiError.value
})
.finally(() => {
apiLoading.value = false
})
}

return { submitProfileEdit, apiLoading, apiError }
}
14 changes: 14 additions & 0 deletions packages/sprinkle-account/app/assets/interfaces/EmailEditApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* API Interfaces - What the API expects and what it returns
*
* This interface is tied to the `ProfileEmailEditAction` API, accessed at the
* POST `/account/settings/email` endpoint.
*
* This api doesn't have a corresponding Response data interface.
* The General API Response interface is used.
*/
// TODO : Email should be it's own form
export interface EmailEditRequest {
email: string
passwordcheck: string
}
15 changes: 15 additions & 0 deletions packages/sprinkle-account/app/assets/interfaces/PasswordEditApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* API Interfaces - What the API expects and what it returns
*
* This interface is tied to the `SettingsEditAction` API, accessed at the
* POST `/account/settings` endpoint.
*
* This api doesn't have a corresponding Response data interface.
* The General API Response interface is used.
*/
// TODO : Email should be it's own form
export interface PasswordEditRequest {
passwordcheck: string
password: string
passwordc: string
}
14 changes: 14 additions & 0 deletions packages/sprinkle-account/app/assets/interfaces/ProfileEditApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* API Interfaces - What the API expects and what it returns
*
* This interface is tied to the `ProfileEditAction` API, accessed at the
* POST `/account/settings/profile` endpoint.
*
* This api doesn't have a corresponding Response data interface.
* The General API Response interface is used.
*/
export interface ProfileEditRequest {
first_name: string
last_name: string
locale: string
}
3 changes: 3 additions & 0 deletions packages/sprinkle-account/app/assets/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ export type { GroupInterface } from './models/groupInterface'
export type { RoleInterface } from './models/roleInterface'
export type { PermissionInterface } from './models/permissionInterface'
export type { RouteGuard } from './routes'
export type { ProfileEditRequest } from './ProfileEditApi'
export type { PasswordEditRequest } from './PasswordEditApi'
export type { EmailEditRequest } from './EmailEditApi'
30 changes: 30 additions & 0 deletions packages/sprinkle-account/app/assets/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,35 @@ export default [
}
},
component: () => import('../views/ResendVerificationView.vue')
},
{
path: '/account/settings',
name: 'account.settings',
redirect: { name: 'account.settings.profile' },
meta: {
auth: {
redirect: { name: 'account.login' }
},
title: 'Account settings',
description: 'Update your account settings, including email, name, and password.'
},
component: () => import('../views/UserSettings.vue'),
children: [
{
path: 'profile',
name: 'account.settings.profile',
component: () => import('../views/UserSettingsProfile.vue')
},
{
path: 'password',
name: 'account.settings.password',
component: () => import('../views/UserSettingsPassword.vue')
},
{
path: 'email',
name: 'account.settings.email',
component: () => import('../views/UserSettingsEmail.vue')
}
]
}
]
3 changes: 3 additions & 0 deletions packages/sprinkle-account/app/assets/views/UserSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<UFPageUserSettings />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<UFPageUserSettingsEmail />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<UFPageUserSettingsPassword />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<UFPageUserSettingsProfile />
</template>
17 changes: 17 additions & 0 deletions packages/sprinkle-account/app/schema/requests/account-email.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
passwordcheck:
validators:
required:
message: PASSWORD.CONFIRM_CURRENT
email:
validators:
required:
label: "&EMAIL"
message: VALIDATE.REQUIRED
length:
label: "&EMAIL"
min: 1
max: 150
message: VALIDATE.LENGTH_RANGE
email:
message: VALIDATE.INVALID_EMAIL
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,6 @@ passwordcheck:
validators:
required:
message: PASSWORD.CONFIRM_CURRENT
email:
validators:
required:
label: "&EMAIL"
message: VALIDATE.REQUIRED
length:
label: "&EMAIL"
min: 1
max: 150
message: VALIDATE.LENGTH_RANGE
email:
message: VALIDATE.INVALID_EMAIL
password:
validators:
length:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use UserFrosting\Alert\AlertStream;
use UserFrosting\Fortress\RequestSchema;
use UserFrosting\Fortress\RequestSchema\RequestSchemaInterface;
use UserFrosting\Fortress\Transformer\RequestDataTransformer;
use UserFrosting\Fortress\Validator\ServerSideValidator;
use UserFrosting\I18n\Translator;
use UserFrosting\Sprinkle\Account\Authenticate\Authenticator;
use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface;
use UserFrosting\Sprinkle\Account\Exceptions\ForbiddenException;
Expand All @@ -40,7 +40,7 @@
* Route Name: settings.profile
* Request type: POST
*/
class ProfileAction
class ProfileEditAction
{
// Request schema to use to validate data.
protected string $schema = 'schema://requests/profile-settings.yaml';
Expand All @@ -49,7 +49,7 @@ class ProfileAction
* Inject dependencies.
*/
public function __construct(
protected AlertStream $alert,
protected Translator $translator,
protected Authenticator $authenticator,
protected SiteLocale $locale,
protected UserActivityLoggerInterface $logger,
Expand All @@ -69,7 +69,14 @@ public function __invoke(Request $request, Response $response): Response
{
$this->handle($request);

return $response;
$payload = json_encode([
// TODO : The message won't be in the right locale if the user
// changed it. We need to find a way to handle this.
'message' => $this->translator->translate('PROFILE.UPDATED')
], JSON_THROW_ON_ERROR);
$response->getBody()->write($payload);

return $response->withHeader('Content-Type', 'application/json');
}

/**
Expand Down Expand Up @@ -126,8 +133,6 @@ protected function handle(Request $request): void
'type' => 'update_profile_settings',
'user_id' => $currentUser->id,
]);

$this->alert->addMessage('success', 'PROFILE.UPDATED');
}

/**
Expand Down
Loading

0 comments on commit 2742816

Please sign in to comment.