From 8a6c085288fddab1be696940d28c40a204c83098 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Fri, 3 Apr 2026 23:12:30 +0200 Subject: [PATCH] Rename company-scoped Users to Members throughout Complete rename across backend and frontend: - Controller: Company/Users/UsersController -> Company/Members/MembersController - Service: UserService -> MemberService - Requests: UserRequest -> MemberRequest, DeleteUserRequest -> DeleteMemberRequest - API routes: /api/v1/users -> /api/v1/members (company-scoped only) - Sidebar menu: "Users" -> "Members" - Frontend: views/users -> views/members, stores/users -> stores/members - Router: users.index -> members.index, /admin/users -> /admin/members - i18n: new "members" section with invitation-related keys - Tests: UserTest -> MemberTest Admin/super-admin Users (system-wide user management) remains unchanged. --- .../General/SerialNumberController.php | 4 +- .../MembersController.php} | 24 ++--- ...serRequest.php => DeleteMemberRequest.php} | 2 +- .../{UserRequest.php => MemberRequest.php} | 2 +- .../{UserService.php => MemberService.php} | 8 +- config/invoiceshelf.php | 6 +- lang/en.json | 46 +++++---- resources/scripts/admin/admin-router.js | 26 ++--- ...exDropdown.vue => MemberIndexDropdown.vue} | 10 +- .../admin/stores/{users.js => members.js} | 24 ++--- resources/scripts/admin/stores/reset.js | 4 +- .../admin/views/{users => members}/Create.vue | 26 ++--- .../admin/views/{users => members}/Index.vue | 38 ++++---- .../scripts/components/GlobalSearchBar.vue | 6 +- routes/api.php | 17 ++-- tests/Feature/Admin/MemberTest.php | 50 ++++++++++ tests/Feature/Admin/RoleTest.php | 2 +- tests/Feature/Admin/UserTest.php | 97 ------------------- 18 files changed, 178 insertions(+), 214 deletions(-) rename app/Http/Controllers/Company/{Users/UsersController.php => Members/MembersController.php} (75%) rename app/Http/Requests/{DeleteUserRequest.php => DeleteMemberRequest.php} (92%) rename app/Http/Requests/{UserRequest.php => MemberRequest.php} (97%) rename app/Services/{UserService.php => MemberService.php} (92%) rename resources/scripts/admin/components/dropdowns/{UserIndexDropdown.vue => MemberIndexDropdown.vue} (87%) rename resources/scripts/admin/stores/{users.js => members.js} (89%) rename resources/scripts/admin/views/{users => members}/Create.vue (91%) rename resources/scripts/admin/views/{users => members}/Index.vue (89%) create mode 100644 tests/Feature/Admin/MemberTest.php delete mode 100644 tests/Feature/Admin/UserTest.php diff --git a/app/Http/Controllers/Company/General/SerialNumberController.php b/app/Http/Controllers/Company/General/SerialNumberController.php index 64b87c4b..3ad47b44 100644 --- a/app/Http/Controllers/Company/General/SerialNumberController.php +++ b/app/Http/Controllers/Company/General/SerialNumberController.php @@ -63,8 +63,8 @@ class SerialNumberController extends Controller public function placeholders(Request $request): JsonResponse { - if ($request->format) { - $placeholders = SerialNumberService::getPlaceholders($request->format); + if ($request->input('format')) { + $placeholders = SerialNumberService::getPlaceholders($request->input('format')); } else { $placeholders = []; } diff --git a/app/Http/Controllers/Company/Users/UsersController.php b/app/Http/Controllers/Company/Members/MembersController.php similarity index 75% rename from app/Http/Controllers/Company/Users/UsersController.php rename to app/Http/Controllers/Company/Members/MembersController.php index 7f185f4f..b7c72ea4 100644 --- a/app/Http/Controllers/Company/Users/UsersController.php +++ b/app/Http/Controllers/Company/Members/MembersController.php @@ -1,20 +1,20 @@ authorize('create', User::class); - $user = $this->userService->create($request); + $user = $this->memberService->create($request); return new UserResource($user); } @@ -73,11 +73,11 @@ class UsersController extends Controller * * @return JsonResponse */ - public function update(UserRequest $request, User $user) + public function update(MemberRequest $request, User $user) { $this->authorize('update', $user); - $this->userService->update($user, $request); + $this->memberService->update($user, $request); return new UserResource($user); } @@ -88,12 +88,12 @@ class UsersController extends Controller * @param Request $request * @return JsonResponse */ - public function delete(DeleteUserRequest $request) + public function delete(DeleteMemberRequest $request) { $this->authorize('delete multiple users', User::class); if ($request->users) { - $this->userService->delete($request->users); + $this->memberService->delete($request->users); } return response()->json([ diff --git a/app/Http/Requests/DeleteUserRequest.php b/app/Http/Requests/DeleteMemberRequest.php similarity index 92% rename from app/Http/Requests/DeleteUserRequest.php rename to app/Http/Requests/DeleteMemberRequest.php index aafb05aa..772b9fed 100644 --- a/app/Http/Requests/DeleteUserRequest.php +++ b/app/Http/Requests/DeleteMemberRequest.php @@ -5,7 +5,7 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class DeleteUserRequest extends FormRequest +class DeleteMemberRequest extends FormRequest { /** * Determine if the user is authorized to make this request. diff --git a/app/Http/Requests/UserRequest.php b/app/Http/Requests/MemberRequest.php similarity index 97% rename from app/Http/Requests/UserRequest.php rename to app/Http/Requests/MemberRequest.php index d456ce83..1e05bc59 100644 --- a/app/Http/Requests/UserRequest.php +++ b/app/Http/Requests/MemberRequest.php @@ -5,7 +5,7 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -class UserRequest extends FormRequest +class MemberRequest extends FormRequest { /** * Determine if the user is authorized to make this request. diff --git a/app/Services/UserService.php b/app/Services/MemberService.php similarity index 92% rename from app/Services/UserService.php rename to app/Services/MemberService.php index 1084a18e..d4e33a97 100644 --- a/app/Services/UserService.php +++ b/app/Services/MemberService.php @@ -2,14 +2,14 @@ namespace App\Services; -use App\Http\Requests\UserRequest; +use App\Http\Requests\MemberRequest; use App\Models\CompanySetting; use App\Models\User; use Silber\Bouncer\BouncerFacade; -class UserService +class MemberService { - public function create(UserRequest $request): User + public function create(MemberRequest $request): User { $user = User::create($request->getUserPayload()); @@ -29,7 +29,7 @@ class UserService return $user; } - public function update(User $user, UserRequest $request): User + public function update(User $user, MemberRequest $request): User { $user->update($request->getUserPayload()); diff --git a/config/invoiceshelf.php b/config/invoiceshelf.php index bfbe54e7..267d12e9 100644 --- a/config/invoiceshelf.php +++ b/config/invoiceshelf.php @@ -352,11 +352,11 @@ return [ ] : [] ), [ - 'title' => 'navigation.users', + 'title' => 'navigation.members', 'group' => 3, - 'link' => '/admin/users', + 'link' => '/admin/members', 'icon' => 'UsersIcon', - 'name' => 'Users', + 'name' => 'Members', 'owner_only' => true, 'ability' => '', 'model' => '', diff --git a/lang/en.json b/lang/en.json index 8516ea70..2df4079a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -12,6 +12,7 @@ "settings": "Settings", "logout": "Logout", "users": "Users", + "members": "Members", "modules": "Modules", "administration": "Administration", "companies": "Companies", @@ -754,33 +755,42 @@ "what_you_get": "What you get", "sign_up_and_get_token": "Sign up & Get Token" }, - "users": { - "title": "Users", - "users_list": "Users List", + "members": { + "title": "Members", + "members_list": "Members List", "name": "Name", "description": "Description", "added_on": "Added On", "date_of_creation": "Date Of Creation", "action": "Action", - "add_user": "Add User", - "save_user": "Save User", - "update_user": "Update User", - "user": "User | Users", - "add_new_user": "Add New User", - "new_user": "New User", - "edit_user": "Edit User", - "no_users": "No users yet!", - "list_of_users": "This section will contain the list of users.", + "invite_member": "Invite Member", + "add_member": "Add Member", + "save_member": "Save Member", + "update_member": "Update Member", + "member": "Member | Members", + "add_new_member": "Add New Member", + "new_member": "New Member", + "edit_member": "Edit Member", + "no_members": "No members yet!", + "list_of_members": "This section will contain the list of company members.", "email": "Email", "phone": "Phone", "password": "Password", - "user_attached_message": "Cannot delete an item which is already in use", - "confirm_delete": "You will not be able to recover this User | You will not be able to recover these Users", - "created_message": "User created successfully", - "updated_message": "User updated successfully", - "deleted_message": "User deleted successfully | Users deleted successfully", + "member_attached_message": "Cannot delete an item which is already in use", + "confirm_delete": "You will not be able to recover this Member | You will not be able to recover these Members", + "created_message": "Member added successfully", + "updated_message": "Member updated successfully", + "deleted_message": "Member removed successfully | Members removed successfully", + "invited_message": "Invitation sent successfully", + "invitation_cancelled": "Invitation cancelled", "select_company_role": "Select Role for {company}", - "companies": "Companies" + "companies": "Companies", + "pending_invitations": "Pending Invitations", + "no_pending_invitations": "No pending invitations", + "cancel_invitation": "Cancel Invitation", + "role": "Role", + "invited_by": "Invited By", + "sent_at": "Sent At" }, "reports": { "title": "Report", diff --git a/resources/scripts/admin/admin-router.js b/resources/scripts/admin/admin-router.js index 961e7f86..ffc3825d 100644 --- a/resources/scripts/admin/admin-router.js +++ b/resources/scripts/admin/admin-router.js @@ -66,9 +66,9 @@ const ItemCreate = () => import('@/scripts/admin/views/items/Create.vue') const ExpensesIndex = () => import('@/scripts/admin/views/expenses/Index.vue') const ExpenseCreate = () => import('@/scripts/admin/views/expenses/Create.vue') -// Users -const UserIndex = () => import('@/scripts/admin/views/users/Index.vue') -const UserCreate = () => import('@/scripts/admin/views/users/Create.vue') +// Members +const MemberIndex = () => import('@/scripts/admin/views/members/Index.vue') +const MemberCreate = () => import('@/scripts/admin/views/members/Create.vue') // Estimates const EstimateIndex = () => import('@/scripts/admin/views/estimates/Index.vue') @@ -391,24 +391,24 @@ export default [ component: ExpenseCreate, }, - // Users + // Members { - path: 'users', - name: 'users.index', + path: 'members', + name: 'members.index', meta: { isOwner: true }, - component: UserIndex, + component: MemberIndex, }, { - path: 'users/create', + path: 'members/create', meta: { isOwner: true }, - name: 'users.create', - component: UserCreate, + name: 'members.create', + component: MemberCreate, }, { - path: 'users/:id/edit', - name: 'users.edit', + path: 'members/:id/edit', + name: 'members.edit', meta: { isOwner: true }, - component: UserCreate, + component: MemberCreate, }, // Estimates diff --git a/resources/scripts/admin/components/dropdowns/UserIndexDropdown.vue b/resources/scripts/admin/components/dropdowns/MemberIndexDropdown.vue similarity index 87% rename from resources/scripts/admin/components/dropdowns/UserIndexDropdown.vue rename to resources/scripts/admin/components/dropdowns/MemberIndexDropdown.vue index 2d544a1e..dccc996e 100644 --- a/resources/scripts/admin/components/dropdowns/UserIndexDropdown.vue +++ b/resources/scripts/admin/components/dropdowns/MemberIndexDropdown.vue @@ -1,14 +1,14 @@ - {{ isEdit ? $t('users.update_user') : $t('users.save_user') }} + {{ isEdit ? $t('members.update_user') : $t('members.save_user') }} @@ -175,9 +175,9 @@ import { import useVuelidate from '@vuelidate/core' import { ValidateEach } from '@vuelidate/components' import { useI18n } from 'vue-i18n' -import { useUsersStore } from '@/scripts/admin/stores/users' +import { useMembersStore } from '@/scripts/admin/stores/members' -const userStore = useUsersStore() +const userStore = useMembersStore() const { t } = useI18n() const route = useRoute() @@ -189,10 +189,10 @@ let isFetchingInitialData = ref(false) let selectedCompanies = ref([]) let companies = ref([]) -const isEdit = computed(() => route.name === 'users.edit') +const isEdit = computed(() => route.name === 'members.edit') const pageTitle = computed(() => - isEdit.value ? t('users.edit_user') : t('users.new_user') + isEdit.value ? t('members.edit_user') : t('members.new_user') ) const rules = computed(() => { @@ -285,7 +285,7 @@ async function submitUser() { const action = isEdit.value ? userStore.updateUser : userStore.addUser await action(data) - router.push('/admin/users') + router.push('/admin/members') isSaving.value = false } catch (error) { isSaving.value = false diff --git a/resources/scripts/admin/views/users/Index.vue b/resources/scripts/admin/views/members/Index.vue similarity index 89% rename from resources/scripts/admin/views/users/Index.vue rename to resources/scripts/admin/views/members/Index.vue index 79f82c08..724a9e9d 100644 --- a/resources/scripts/admin/views/users/Index.vue +++ b/resources/scripts/admin/views/members/Index.vue @@ -1,10 +1,10 @@ - + - + - + @@ -84,12 +84,12 @@ - {{ $t('users.add_user') }} + {{ $t('members.add_user') }} @@ -188,17 +188,17 @@ import { computed, onUnmounted, ref, reactive, onMounted, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useRouter } from 'vue-router' -import { useUsersStore } from '@/scripts/admin/stores/users' +import { useMembersStore } from '@/scripts/admin/stores/members' import { useNotificationStore } from '@/scripts/stores/notification' import { useDialogStore } from '@/scripts/stores/dialog' import { useUserStore } from '@/scripts/admin/stores/user' import AstronautIcon from '@/scripts/components/icons/empty/AstronautIcon.vue' -import UserDropdown from '@/scripts/admin/components/dropdowns/UserIndexDropdown.vue' +import UserDropdown from '@/scripts/admin/components/dropdowns/MemberIndexDropdown.vue' import abilities from '@/scripts/admin/stub/abilities' const notificationStore = useNotificationStore() const dialogStore = useDialogStore() -const usersStore = useUsersStore() +const usersStore = useMembersStore() const userStore = useUserStore() const router = useRouter() @@ -227,18 +227,18 @@ const userTableColumns = computed(() => { }, { key: 'name', - label: t('users.name'), + label: t('members.name'), thClass: 'extra', tdClass: 'font-medium text-gray-900', }, { key: 'email', label: 'Email' }, { key: 'phone', - label: t('users.phone'), + label: t('members.phone'), }, { key: 'created_at', - label: t('users.added_on'), + label: t('members.added_on'), }, { key: 'actions', @@ -342,7 +342,7 @@ function removeUser(id) { dialogStore .openDialog({ title: t('general.are_you_sure'), - message: t('users.confirm_delete', 1), + message: t('members.confirm_delete', 1), yesLabel: t('general.ok'), noLabel: t('general.cancel'), variant: 'danger', @@ -361,7 +361,7 @@ function removeUser(id) { if (response.data.error === 'user_attached') { notificationStore.showNotification({ type: 'error', - message: t('users.user_attached_message'), + message: t('members.user_attached_message'), }) return true } @@ -374,7 +374,7 @@ function removeMultipleUsers() { dialogStore .openDialog({ title: t('general.are_you_sure'), - message: t('users.confirm_delete', 2), + message: t('members.confirm_delete', 2), yesLabel: t('general.ok'), noLabel: t('general.cancel'), variant: 'danger', diff --git a/resources/scripts/components/GlobalSearchBar.vue b/resources/scripts/components/GlobalSearchBar.vue index 50f25e03..015d49be 100644 --- a/resources/scripts/components/GlobalSearchBar.vue +++ b/resources/scripts/components/GlobalSearchBar.vue @@ -119,7 +119,7 @@ class="p-2 hover:bg-gray-100 cursor-pointer rounded-md" > import { ref, watch } from 'vue' -import { useUsersStore } from '@/scripts/admin/stores/users' +import { useMembersStore } from '@/scripts/admin/stores/members' import { onClickOutside } from '@vueuse/core' import { useRoute } from 'vue-router' import SpinnerIcon from '@/scripts/components/icons/SpinnerIcon.vue' import { debounce } from 'lodash' -const usersStore = useUsersStore() +const usersStore = useMembersStore() const isShow = ref(false) const name = ref('') diff --git a/routes/api.php b/routes/api.php index cc400f64..c645a772 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,6 +11,7 @@ use App\Http\Controllers\Admin\Settings\MailConfigurationController; use App\Http\Controllers\Admin\Settings\PDFConfigurationController; use App\Http\Controllers\Admin\Settings\SettingsController; use App\Http\Controllers\Admin\UpdateController; +use App\Http\Controllers\Admin\UsersController; use App\Http\Controllers\AppVersionController; use App\Http\Controllers\Company\Auth\AuthController; use App\Http\Controllers\Company\Auth\ForgotPasswordController; @@ -35,6 +36,7 @@ use App\Http\Controllers\Company\Invoice\InvoicesController; use App\Http\Controllers\Company\Invoice\InvoiceTemplatesController; use App\Http\Controllers\Company\Item\ItemsController; use App\Http\Controllers\Company\Item\UnitsController; +use App\Http\Controllers\Company\Members\MembersController; use App\Http\Controllers\Company\Payment\PaymentMethodsController; use App\Http\Controllers\Company\Payment\PaymentsController; use App\Http\Controllers\Company\RecurringInvoice\RecurringInvoiceController; @@ -47,7 +49,6 @@ use App\Http\Controllers\Company\Settings\CompanySettingsController; use App\Http\Controllers\Company\Settings\InvitationController; use App\Http\Controllers\Company\Settings\TaxTypesController; use App\Http\Controllers\Company\Settings\UserProfileController; -use App\Http\Controllers\Company\Users\UsersController; use App\Http\Controllers\CustomerPortal\Auth\ForgotPasswordController as AuthForgotPasswordController; use App\Http\Controllers\CustomerPortal\Auth\ResetPasswordController as AuthResetPasswordController; use App\Http\Controllers\CustomerPortal\Estimate\AcceptEstimateController as CustomerAcceptEstimateController; @@ -154,15 +155,15 @@ Route::prefix('/v1')->group(function () { Route::get('companies/{company}', [CompaniesController::class, 'show']); Route::put('companies/{company}', [CompaniesController::class, 'update']); - Route::get('users', [App\Http\Controllers\Admin\UsersController::class, 'index']); - Route::get('users/{user}', [App\Http\Controllers\Admin\UsersController::class, 'show']); - Route::put('users/{user}', [App\Http\Controllers\Admin\UsersController::class, 'update']); - Route::post('users/{user}/impersonate', [App\Http\Controllers\Admin\UsersController::class, 'impersonate']); + Route::get('users', [UsersController::class, 'index']); + Route::get('users/{user}', [UsersController::class, 'show']); + Route::put('users/{user}', [UsersController::class, 'update']); + Route::post('users/{user}/impersonate', [UsersController::class, 'impersonate']); }); // Stop impersonation - uses auth:sanctum only (the impersonated user's token, not super-admin) Route::middleware(['auth:sanctum'])->prefix('super-admin')->group(function () { - Route::post('stop-impersonating', [App\Http\Controllers\Admin\UsersController::class, 'stopImpersonating']); + Route::post('stop-impersonating', [UsersController::class, 'stopImpersonating']); }); Route::middleware(['auth:sanctum', 'company'])->group(function () { @@ -441,9 +442,9 @@ Route::prefix('/v1')->group(function () { // Users // ---------------------------------- - Route::post('/users/delete', [UsersController::class, 'delete']); + Route::post('/members/delete', [MembersController::class, 'delete']); - Route::apiResource('/users', UsersController::class); + Route::apiResource('/members', MembersController::class); // Modules // ---------------------------------- diff --git a/tests/Feature/Admin/MemberTest.php b/tests/Feature/Admin/MemberTest.php new file mode 100644 index 00000000..10143597 --- /dev/null +++ b/tests/Feature/Admin/MemberTest.php @@ -0,0 +1,50 @@ + 'DatabaseSeeder', '--force' => true]); + Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]); + + $user = User::where('role', 'super admin')->first(); + + $this->withHeaders([ + 'company' => $user->companies()->first()->id, + ]); + + Sanctum::actingAs( + $user, + ['*'] + ); +}); + +test('list members', function () { + getJson('/api/v1/members')->assertOk(); +}); + +test('store member using a form request', function () { + $this->assertActionUsesFormRequest( + MembersController::class, + 'store', + MemberRequest::class + ); +}); + +test('get member', function () { + $user = User::factory()->create(); + + getJson("/api/v1/members/{$user->id}")->assertOk(); +}); + +test('update member using a form request', function () { + $this->assertActionUsesFormRequest( + MembersController::class, + 'update', + MemberRequest::class + ); +}); diff --git a/tests/Feature/Admin/RoleTest.php b/tests/Feature/Admin/RoleTest.php index 73395b4c..18cfd555 100644 --- a/tests/Feature/Admin/RoleTest.php +++ b/tests/Feature/Admin/RoleTest.php @@ -33,7 +33,7 @@ test('create super admin role', function () { ], ]; - postJson('api/v1/users', $data) + postJson('api/v1/members', $data) ->assertStatus(201); $data = collect($data) diff --git a/tests/Feature/Admin/UserTest.php b/tests/Feature/Admin/UserTest.php deleted file mode 100644 index 05357857..00000000 --- a/tests/Feature/Admin/UserTest.php +++ /dev/null @@ -1,97 +0,0 @@ - 'DatabaseSeeder', '--force' => true]); - Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]); - - $user = User::where('role', 'super admin')->first(); - - $this->withHeaders([ - 'company' => $user->companies()->first()->id, - ]); - - Sanctum::actingAs( - $user, - ['*'] - ); -}); - -getJson('/api/v1/users')->assertOk(); - -test('store user using a form request', function () { - $this->assertActionUsesFormRequest( - UsersController::class, - 'store', - UserRequest::class - ); -}); - -// test('store user', function () { -// $data = [ -// 'name' => fake()->name, -// 'email' => fake()->unique()->safeEmail, -// 'phone' => fake()->phoneNumber, -// 'password' => fake()->password -// ]; - -// postJson('/api/v1/users', $data)->assertOk(); - -// $this->assertDatabaseHas('users', [ -// 'name' => $data['name'], -// 'email' => $data['email'], -// 'phone' => $data['phone'], -// ]); -// }); - -test('get user', function () { - $user = User::factory()->create(); - - getJson("/api/v1/users/{$user->id}")->assertOk(); -}); - -test('update user using a form request', function () { - $this->assertActionUsesFormRequest( - UsersController::class, - 'update', - UserRequest::class - ); -}); - -// test('update user', function () { -// $user = User::factory()->create(); - -// $data = [ -// 'name' => fake()->name, -// 'email' => fake()->unique()->safeEmail, -// 'phone' => fake()->phoneNumber, -// 'password' => fake()->password -// ]; - -// putJson("/api/v1/users/{$user->id}", $data)->assertOk(); - -// $this->assertDatabaseHas('users', [ -// 'name' => $data['name'], -// 'email' => $data['email'], -// 'phone' => $data['phone'], -// ]); -// }); - -// test('delete users', function () { -// $user = User::factory()->create(); -// $data['users'] = [$user->id]; - -// postJson("/api/v1/users/delete", $data) -// ->assertOk(); - -// $this->assertModelMissing($user); -// });