Add workspace update functionality (#699)

* Add workspace update functionality

* Refactor workspace settings header layout and edit button styling

* Update workspace route and API endpoint to use root path

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Chirag Chhatrala 2025-02-12 18:13:55 +05:30 committed by GitHub
parent 1f9a1f835f
commit aae28d09cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 95 additions and 21 deletions

View File

@ -91,4 +91,24 @@ class WorkspaceController extends Controller
'workspace' => new WorkspaceResource($workspace), 'workspace' => new WorkspaceResource($workspace),
]); ]);
} }
public function update(Request $request, $id)
{
$workspace = Auth::user()->workspaces()->findOrFail($id);
$this->authorize('update', $workspace);
$this->validate($request, [
'name' => 'required',
]);
$workspace->update([
'name' => $request->name,
'icon' => $request->emoji ?? '',
]);
return $this->success([
'message' => 'Workspace updated.',
'workspace' => new WorkspaceResource($workspace),
]);
}
} }

View File

@ -50,7 +50,7 @@ class WorkspacePolicy
*/ */
public function update(User $user, Workspace $workspace) public function update(User $user, Workspace $workspace)
{ {
return false; return $user->ownsWorkspace($workspace);
} }
/** /**

View File

@ -143,6 +143,7 @@ Route::group(['middleware' => 'auth:api'], function () {
)->name('forms.index'); )->name('forms.index');
Route::put('/custom-domains', [WorkspaceController::class, 'saveCustomDomain'])->name('save-custom-domains'); Route::put('/custom-domains', [WorkspaceController::class, 'saveCustomDomain'])->name('save-custom-domains');
Route::put('/email-settings', [WorkspaceController::class, 'saveEmailSettings'])->name('save-email-settings'); Route::put('/email-settings', [WorkspaceController::class, 'saveEmailSettings'])->name('save-email-settings');
Route::put('/', [WorkspaceController::class, 'update'])->name('update');
Route::delete('/', [WorkspaceController::class, 'delete'])->name('delete'); Route::delete('/', [WorkspaceController::class, 'delete'])->name('delete');
Route::middleware('pro-form')->group(function () { Route::middleware('pro-form')->group(function () {

View File

@ -34,3 +34,30 @@ it('can create and delete Workspace', function () {
} }
} }
}); });
it('can update workspace', function () {
$user = $this->actingAsUser();
$this->postJson(route('open.workspaces.create'), [
'name' => 'Workspace Test',
'icon' => '🧪',
])
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Workspace created.',
]);
expect($user->workspaces()->count())->toBe(1);
$workspace = $user->workspaces()->first();
$this->putJson(route('open.workspaces.update', $workspace->id), [
'name' => 'Workspace Test Updated',
'icon' => '🔬',
])
->assertSuccessful()
->assertJson([
'type' => 'success',
'message' => 'Workspace updated.',
]);
});

View File

@ -2,9 +2,20 @@
<div> <div>
<div class="flex flex-wrap items-center gap-y-4"> <div class="flex flex-wrap items-center gap-y-4">
<div class="flex-grow"> <div class="flex-grow">
<div class="flex items-center gap-2">
<h3 class="font-semibold text-2xl text-gray-900"> <h3 class="font-semibold text-2xl text-gray-900">
Workspace settings Workspace settings
</h3> </h3>
<UTooltip text="Edit workspace">
<UButton
v-if="!workspace.is_readonly"
size="xs"
color="white"
icon="i-heroicons-pencil-square"
@click="editCurrentWorkspace"
/>
</UTooltip>
</div>
<small class="text-gray-500">You're currently editing the settings for the workspace "{{ workspace.name }}". <small class="text-gray-500">You're currently editing the settings for the workspace "{{ workspace.name }}".
You can switch to another workspace in top left corner of the page.</small> You can switch to another workspace in top left corner of the page.</small>
</div> </div>
@ -39,30 +50,21 @@
<modal <modal
:show="workspaceModal" :show="workspaceModal"
max-width="lg" max-width="lg"
:compact-header="true"
@close="workspaceModal = false" @close="workspaceModal = false"
> >
<template #icon> <template #icon>
<svg <Icon
:name="isEditing ? 'heroicons:pencil-square' : 'heroicons:plus-circle'"
class="w-8 h-8" class="w-8 h-8"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 8V16M8 12H16M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/> />
</svg>
</template> </template>
<template #title> <template #title>
Create Workspace {{ isEditing ? 'Edit' : 'Create' }} Workspace
</template> </template>
<div class="px-4"> <div class="px-4">
<form <form
@submit.prevent="createWorkspace" @submit.prevent="isEditing ? updateWorkspace() : createWorkspace()"
@keydown="form.onKeydown($event)" @keydown="form.onKeydown($event)"
> >
<div> <div>
@ -115,11 +117,28 @@ const form = useForm({
emoji: "", emoji: "",
}) })
const workspaceModal = ref(false) const workspaceModal = ref(false)
const isEditing = ref(false)
onMounted(() => { onMounted(() => {
fetchAllWorkspaces() fetchAllWorkspaces()
}) })
const editCurrentWorkspace = () => {
isEditing.value = true
form.name = workspace.value.name
form.emoji = workspace.value.icon || ''
workspaceModal.value = true
}
const updateWorkspace = () => {
form.put(`/open/workspaces/${workspace.value.id}/`).then((data) => {
workspacesStore.save(data.workspace)
workspaceModal.value = false
isEditing.value = false
useAlert().success('Workspace successfully updated!')
})
}
const createWorkspace = () => { const createWorkspace = () => {
form.post("/open/workspaces/create").then((data) => { form.post("/open/workspaces/create").then((data) => {
workspacesStore.save(data.workspace) workspacesStore.save(data.workspace)
@ -130,4 +149,11 @@ const createWorkspace = () => {
) )
}) })
} }
watch(workspaceModal, (newValue) => {
if (!newValue) {
isEditing.value = false
form.reset()
}
})
</script> </script>