From 59142c94654a64d33a06b0b115fe63dccd95455d Mon Sep 17 00:00:00 2001 From: Louis Charette Date: Fri, 27 Dec 2024 22:57:11 -0500 Subject: [PATCH] Enable/Disable User --- .../app/assets/composables/index.ts | 1 + .../assets/composables/useUserUpdateApi.ts | 49 ++++++++ .../app/assets/interfaces/ApiResponse.ts | 6 + .../app/assets/interfaces/index.ts | 1 + .../Controller/User/UserUpdateFieldAction.php | 39 ++++--- .../User/UserUpdateFieldActionTest.php | 79 +++---------- .../Pages/Admin/User/UserActivateModal.vue | 106 ++++++++++++++++++ .../components/Pages/Admin/User/UserInfo.vue | 9 +- .../src/views/Admin/UsersPage.vue | 8 +- 9 files changed, 214 insertions(+), 84 deletions(-) create mode 100644 packages/sprinkle-admin/app/assets/composables/useUserUpdateApi.ts create mode 100644 packages/sprinkle-admin/app/assets/interfaces/ApiResponse.ts create mode 100644 packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserActivateModal.vue diff --git a/packages/sprinkle-admin/app/assets/composables/index.ts b/packages/sprinkle-admin/app/assets/composables/index.ts index 5b5da8a83..71b2a5909 100644 --- a/packages/sprinkle-admin/app/assets/composables/index.ts +++ b/packages/sprinkle-admin/app/assets/composables/index.ts @@ -18,3 +18,4 @@ export { useUserApi } from './useUserApi' export { useUserCreateApi } from './useUserCreateApi' export { useUserDeleteApi } from './useUserDeleteApi' export { useUserEditApi } from './useUserEditApi' +export { useUserUpdateApi } from './useUserUpdateApi' diff --git a/packages/sprinkle-admin/app/assets/composables/useUserUpdateApi.ts b/packages/sprinkle-admin/app/assets/composables/useUserUpdateApi.ts new file mode 100644 index 000000000..de8c4fa97 --- /dev/null +++ b/packages/sprinkle-admin/app/assets/composables/useUserUpdateApi.ts @@ -0,0 +1,49 @@ +import { ref } from 'vue' +import axios from 'axios' +import { Severity, type AlertInterface } from '@userfrosting/sprinkle-core/interfaces' +import type { ApiResponse } from '../interfaces' + +// TODO : Add validation +// 'schema://requests/user/edit-field.yaml' + +/** + * API Composable + */ +export function useUserUpdateApi() { + const apiLoading = ref(false) + const apiError = ref(null) + + async function submitUserUpdate(user_name: string, fieldName: string, fieldValue: any) { + apiLoading.value = true + apiError.value = null + + // Assign the field name and value to the payload + const payload: Record = {} + payload[fieldName] = fieldValue + + return axios + .put('/api/users/u/' + user_name + '/' + fieldName, payload) + .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 { submitUserUpdate, apiLoading, apiError } +} diff --git a/packages/sprinkle-admin/app/assets/interfaces/ApiResponse.ts b/packages/sprinkle-admin/app/assets/interfaces/ApiResponse.ts new file mode 100644 index 000000000..ca6d05bcb --- /dev/null +++ b/packages/sprinkle-admin/app/assets/interfaces/ApiResponse.ts @@ -0,0 +1,6 @@ +/** + * Interfaces - What the API expects and what it returns + */ +export interface ApiResponse { + message: string +} diff --git a/packages/sprinkle-admin/app/assets/interfaces/index.ts b/packages/sprinkle-admin/app/assets/interfaces/index.ts index 76bf636e0..fd9598da9 100644 --- a/packages/sprinkle-admin/app/assets/interfaces/index.ts +++ b/packages/sprinkle-admin/app/assets/interfaces/index.ts @@ -9,3 +9,4 @@ export type { UserApi } from './UserApi' export type { UserCreateForm, UserCreateResponse } from './UserCreateApi' export type { UserDeleteResponse } from './UserDeleteApi' export type { UserEditForm, UserEditResponse } from './UserEditApi' +export type { ApiResponse } from './ApiResponse' diff --git a/packages/sprinkle-admin/app/src/Controller/User/UserUpdateFieldAction.php b/packages/sprinkle-admin/app/src/Controller/User/UserUpdateFieldAction.php index 738fd689e..8eead6b2d 100644 --- a/packages/sprinkle-admin/app/src/Controller/User/UserUpdateFieldAction.php +++ b/packages/sprinkle-admin/app/src/Controller/User/UserUpdateFieldAction.php @@ -16,19 +16,21 @@ use Illuminate\Support\Collection; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; -use UserFrosting\Alert\AlertStream; use UserFrosting\Config\Config; 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\Database\Models\User; use UserFrosting\Sprinkle\Account\Exceptions\AccountException; use UserFrosting\Sprinkle\Account\Exceptions\ForbiddenException; use UserFrosting\Sprinkle\Account\Log\UserActivityLogger; use UserFrosting\Sprinkle\Admin\Exceptions\MissingRequiredParamException; use UserFrosting\Sprinkle\Core\Exceptions\ValidationException; +use UserFrosting\Support\Message\UserMessage; /** * Processes the request to update a specific field for an existing user. @@ -51,7 +53,7 @@ class UserUpdateFieldAction * Inject dependencies. */ public function __construct( - protected AlertStream $alert, + protected Translator $translator, protected Authenticator $authenticator, protected Config $config, protected Connection $db, @@ -76,8 +78,10 @@ public function __invoke( Request $request, Response $response ): Response { - $this->handle($user, $field, $request); - $payload = json_encode([], JSON_THROW_ON_ERROR); + $message = $this->handle($user, $field, $request); + $payload = json_encode([ + 'message' => $this->translator->translate($message->message, $message->parameters), + ], JSON_THROW_ON_ERROR); $response->getBody()->write($payload); return $response->withHeader('Content-Type', 'application/json'); @@ -89,12 +93,14 @@ public function __invoke( * @param UserInterface $user * @param string $fieldName * @param Request $request + * + * @return UserMessage The message to display to the user. */ protected function handle( UserInterface $user, string $fieldName, Request $request - ): void { + ): UserMessage { // Access-controlled resource - check that current User has permission // to edit the specified field for this user $this->validateAccess($user, $fieldName); @@ -189,24 +195,21 @@ protected function handle( ]); }); - // Add success messages + // Return success messages + $message = new UserMessage(); + $message->parameters = ['user_name' => $user->user_name]; + if ($fieldName === 'flag_enabled' && $fieldValue === '1') { - $this->alert->addMessage('success', 'ENABLE_SUCCESSFUL', [ - 'user_name' => $user->user_name, - ]); + $message->message = 'ENABLE_SUCCESSFUL'; } elseif ($fieldName === 'flag_enabled') { - $this->alert->addMessage('success', 'DISABLE_SUCCESSFUL', [ - 'user_name' => $user->user_name, - ]); + $message->message = 'DISABLE_SUCCESSFUL'; } elseif ($fieldName == 'flag_verified') { - $this->alert->addMessage('success', 'MANUALLY_ACTIVATED', [ - 'user_name' => $user->user_name, - ]); + $message->message = 'MANUALLY_ACTIVATED'; } else { - $this->alert->addMessage('success', 'DETAILS_UPDATED', [ - 'user_name' => $user->user_name, - ]); + $message->message = 'DETAILS_UPDATED'; } + + return $message; } /** diff --git a/packages/sprinkle-admin/app/tests/Controller/User/UserUpdateFieldActionTest.php b/packages/sprinkle-admin/app/tests/Controller/User/UserUpdateFieldActionTest.php index 663089b0a..00a3b0a69 100644 --- a/packages/sprinkle-admin/app/tests/Controller/User/UserUpdateFieldActionTest.php +++ b/packages/sprinkle-admin/app/tests/Controller/User/UserUpdateFieldActionTest.php @@ -13,7 +13,6 @@ namespace UserFrosting\Sprinkle\Admin\Tests\Controller\User; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use UserFrosting\Alert\AlertStream; use UserFrosting\Config\Config; use UserFrosting\Sprinkle\Account\Database\Models\Role; use UserFrosting\Sprinkle\Account\Database\Models\User; @@ -121,15 +120,10 @@ public function testPostForPassword(): void $response = $this->handleRequest($request); // Assert response status & body - $this->assertJsonResponse([], $response); $this->assertResponseStatus(200, $response); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('success', array_reverse($messages)[0]['type']); - $this->assertSame('Account details updated for user ' . $userToEdit->user_name . '', array_reverse($messages)[0]['message']); + $this->assertJsonResponse([ + 'message' => 'Account details updated for user ' . $userToEdit->user_name . '', + ], $response); } public function testPostForPasswordWithoutConfirmation(): void @@ -167,15 +161,10 @@ public function testPostForEnabled(): void $response = $this->handleRequest($request); // Assert response status & body - $this->assertJsonResponse([], $response); $this->assertResponseStatus(200, $response); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('success', array_reverse($messages)[0]['type']); - $this->assertSame('Account for user ' . $user->user_name . ' has been successfully enabled.', array_reverse($messages)[0]['message']); + $this->assertJsonResponse([ + 'message' => 'Account for user ' . $user->user_name . ' has been successfully enabled.', + ], $response); } public function testPostForDisabled(): void @@ -194,15 +183,10 @@ public function testPostForDisabled(): void $response = $this->handleRequest($request); // Assert response status & body - $this->assertJsonResponse([], $response); $this->assertResponseStatus(200, $response); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('success', array_reverse($messages)[0]['type']); - $this->assertSame('Account for user ' . $userToEdit->user_name . ' has been successfully disabled.', array_reverse($messages)[0]['message']); + $this->assertJsonResponse([ + 'message' => 'Account for user ' . $userToEdit->user_name . ' has been successfully disabled.', + ], $response); } public function testPostForVerified(): void @@ -217,15 +201,10 @@ public function testPostForVerified(): void $response = $this->handleRequest($request); // Assert response status & body - $this->assertJsonResponse([], $response); $this->assertResponseStatus(200, $response); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('success', array_reverse($messages)[0]['type']); - $this->assertSame($user->user_name . "'s account has been manually activated", array_reverse($messages)[0]['message']); + $this->assertJsonResponse([ + 'message' => $user->user_name . "'s account has been manually activated", + ], $response); } public function testPostForRole(): void @@ -255,19 +234,14 @@ public function testPostForRole(): void $response = $this->handleRequest($request); // Assert response status & body - $this->assertJsonResponse([], $response); $this->assertResponseStatus(200, $response); + $this->assertJsonResponse([ + 'message' => 'Account details updated for user ' . $user->user_name . '', + ], $response); // Make sure the user has the new roles. $user->refresh(); $this->assertCount(2, $user->roles); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('success', array_reverse($messages)[0]['type']); - $this->assertSame('Account details updated for user ' . $user->user_name . '', array_reverse($messages)[0]['message']); } public function testPostForRemovingRoles(): void @@ -283,19 +257,14 @@ public function testPostForRemovingRoles(): void $response = $this->handleRequest($request); // Assert response status & body - $this->assertJsonResponse([], $response); $this->assertResponseStatus(200, $response); + $this->assertJsonResponse([ + 'message' => 'Account details updated for user ' . $user->user_name . '', + ], $response); // Make sure the user has the new roles. $user->refresh(); $this->assertCount(0, $user->roles); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('success', array_reverse($messages)[0]['type']); - $this->assertSame('Account details updated for user ' . $user->user_name . '', array_reverse($messages)[0]['message']); } public function testPageForFailedValidation(): void @@ -316,12 +285,6 @@ public function testPageForFailedValidation(): void // Assert response status & body $this->assertJsonResponse('Invalid email address.', $response, 'description'); $this->assertResponseStatus(400, $response); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('danger', array_reverse($messages)[0]['type']); } public function testPageForFailedToEditMasterUser(): void @@ -346,12 +309,6 @@ public function testPageForFailedToEditMasterUser(): void // Assert response status & body $this->assertJsonResponse('Access Denied', $response, 'title'); $this->assertResponseStatus(403, $response); - - // Test message - /** @var AlertStream */ - $ms = $this->ci->get(AlertStream::class); - $messages = $ms->getAndClearMessages(); - $this->assertSame('danger', array_reverse($messages)[0]['type']); } public function testPostForDisableMasterUser(): void diff --git a/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserActivateModal.vue b/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserActivateModal.vue new file mode 100644 index 000000000..9a30c231c --- /dev/null +++ b/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserActivateModal.vue @@ -0,0 +1,106 @@ + + + diff --git a/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserInfo.vue b/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserInfo.vue index e2dc33599..408849211 100644 --- a/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserInfo.vue +++ b/packages/theme-pink-cupcake/src/components/Pages/Admin/User/UserInfo.vue @@ -6,6 +6,7 @@ import { Severity } from '@userfrosting/sprinkle-core/interfaces' import type { UserApi } from '@userfrosting/sprinkle-admin/interfaces' import UserEditModal from './UserEditModal.vue' import UserDeleteModal from './UserDeleteModal.vue' +import UserActivateModal from './UserActivateModal.vue' const router = useRouter() const { user } = defineProps<{ @@ -58,10 +59,10 @@ const emits = defineEmits(['updated']) class="uk-button uk-button-default uk-width-1-1 uk-margin-small-bottom uk-button-small"> Change User Password - +