Team functionality (#459)

* add api enpoints for adding, removing, updating user to workspace and leaving workspace

* feat: updates client site workspace settings

* refactor and add domain setting ui in modal

* move workspace user functionality to its own component

* adds tests

* fix linting

* updates select input to FlatSelectInput

* moves workspace user role edit to seperated component

* move user adding to its own component

* adds check to usure users exist before checking is admin

* fix loading users

* feat: invite user to team functionality

* fix token coulmn

* fix self host mode changes

* tests for  user invite

* Refactor back-end

* Rename variables

* Improve some styling elements + refactor workspace settings

* More styling

* More UI polishing

* More UI fixes

* PHP linting

* Implemented most of the logic for team-functionnality

* Fix user avatar URL

* WIP remove users on cancellation

* Finished pricing for team functionality

* Fix tests

* Fix linting

* Added pricing_enabled helper

* Fix pricing_enabled shortcut

* Debug CI

* Disable pricing when testing

---------

Co-authored-by: LL-Etiane <lukongleinyuyetiane@gmail.com>
Co-authored-by: Lukong Etiane <83535251+LL-Etiane@users.noreply.github.com>
Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Favour Olayinka
2024-07-04 16:21:36 +01:00
committed by GitHub
parent 383fff7b2c
commit 90ff91b1e9
64 changed files with 2503 additions and 596 deletions

View File

@@ -15,7 +15,7 @@
>
<div
v-if="closeable"
class="absolute top-4 right-4"
class="absolute top-4 right-4 z-10"
>
<button
class="text-gray-500 hover:text-gray-900 cursor-pointer"

View File

@@ -23,19 +23,19 @@
</div>
<div
v-if="showAuth"
class="hidden md:block ml-auto relative"
class="hidden md:flex gap-x-2 ml-auto"
>
<NuxtLink
v-if="$route.name !== 'templates'"
:to="{ name: 'templates' }"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8"
:class="navLinkClasses"
>
Templates
</NuxtLink>
<template v-if="featureBaseEnabled">
<button
v-if="user"
class="text-sm text-gray-600 dark:text-white hidden sm:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
:class="navLinkClasses"
@click.prevent="openChangelog"
>
What's new? <span
@@ -48,7 +48,7 @@
v-else
:href="opnformConfig.links.changelog_url"
target="_blank"
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
:class="navLinkClasses"
>
What's new?
</a>
@@ -56,7 +56,8 @@
<NuxtLink
v-if="$route.name !== 'ai-form-builder' && user === null"
:to="{ name: 'ai-form-builder' }"
class="text-sm text-gray-600 dark:text-white hidden lg:inline hover:text-gray-800 cursor-pointer mt-1 mr-8"
:class="navLinkClasses"
class="hidden lg:inline"
>
AI Form Builder
</NuxtLink>
@@ -67,15 +68,18 @@
$route.name !== 'pricing'
"
:to="{ name: 'pricing' }"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1 mr-8"
:class="navLinkClasses"
>
<span v-if="user">Upgrade</span>
<span
v-if="user"
class="text-primary"
>Upgrade</span>
<span v-else>Pricing</span>
</NuxtLink>
<NuxtLink
:href="helpUrl"
class="text-sm text-gray-600 dark:text-white hover:text-gray-800 cursor-pointer mt-1"
:class="navLinkClasses"
target="_blank"
>
Help
@@ -90,7 +94,7 @@
class="block"
>
<div class="flex items-center">
<div class="ml-3 mr-4 relative">
<div class="ml-4 relative">
<div class="relative inline-block text-left">
<dropdown
v-if="user"
@@ -100,7 +104,8 @@
<button
id="dropdown-menu-button"
type="button"
class="flex items-center justify-center w-full rounded-md px-4 py-2 text-sm text-gray-700 dark:text-gray-50 hover:bg-gray-50 dark:hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-gray-500"
:class="navLinkClasses"
class="flex items-center"
dusk="nav-dropdown-button"
@click.stop="toggle()"
>
@@ -117,7 +122,7 @@
<NuxtLink
v-if="userOnboarded"
:to="{ name: 'home' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:no-underline transition-colors hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -139,7 +144,7 @@
<NuxtLink
v-if="userOnboarded"
:to="{ name: 'templates-my-templates' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:no-underline transition-colors hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -160,7 +165,7 @@
<NuxtLink
:to="{ name: 'settings-profile' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:no-underline transition-colors hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg
class="w-4 h-4 mr-2"
@@ -188,7 +193,7 @@
<NuxtLink
v-if="user.moderator"
:to="{ name: 'settings-admin' }"
class="block block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
class="block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:no-underline transition-colors hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -209,7 +214,7 @@
<a
href="#"
class="block block px-4 py-2 text-md text-gray-700 dark:text-white hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
class="block px-4 py-2 text-md text-gray-700 hover:no-underline transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
@click.prevent="logout"
>
<svg
@@ -236,7 +241,7 @@
<NuxtLink
v-if="$route.name !== 'login'"
:to="{ name: 'login' }"
class="text-gray-600 dark:text-white hover:text-gray-800 dark:hover:text-white px-0 sm:px-3 py-2 rounded-md text-sm"
:class="navLinkClasses"
active-class="text-gray-800 dark:text-white"
>
Login
@@ -245,6 +250,7 @@
<v-button
v-track.nav_create_form_click
size="small"
class="shrink-0"
:to="{ name: 'forms-create-guest' }"
color="outline-blue"
:arrow="true"
@@ -290,6 +296,10 @@ export default {
}
},
data: () => ({
navLinkClasses: 'border border-transparent hover:border-gray-200 text-gray-500 hover:text-gray-800 hover:no-underline dark:hover:text-white py-2 px-3 hover:bg-gray-50 rounded-md text-sm font-medium transition-colors w-full md:w-auto text-center md:text-left'
}),
computed: {
helpUrl() {
return this.opnformConfig.links.help_url

View File

@@ -1,5 +1,5 @@
<template>
<dropdown
<Dropdown
v-if="user && workspaces && workspaces.length > 1"
ref="dropdown"
dropdown-class="origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-50"
@@ -10,72 +10,47 @@
#trigger="{ toggle }"
>
<div
class="flex items-center cursor group"
class="flex items-center cursor border border-transparent hover:border-gray-200 py-2 px-3 hover:bg-gray-50 rounded-md transition-colors"
role="button"
@click.stop="toggle()"
>
<div class="rounded-full h-8 8">
<img
v-if="isUrl(workspace.icon)"
:src="workspace.icon"
:alt="workspace.name + ' icon'"
class="flex-shrink-0 h-8 w-8 rounded-full shadow"
>
<div
v-else
class="rounded-full pt-2 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
v-text="workspace.icon"
/>
</div>
<WorkspaceIcon :workspace="workspace" />
<p
class="hidden group-hover:underline lg:block max-w-10 truncate ml-2 text-gray-800 dark:text-gray-200"
class="hidden md:block max-w-10 truncate text-sm ml-2 text-gray-800 dark:text-gray-200"
>
{{ workspace.name }}
</p>
</div>
</template>
<template
v-for="worksp in workspaces"
:key="worksp.id"
>
<div class="px-1">
<a
v-for="worksp in workspaces"
:key="worksp.id"
href="#"
class="px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
class="px-4 py-2 text-md rounded text-gray-700 hover:no-underline hover:bg-neutral-50 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600 flex items-center"
:class="{
'bg-blue-100 dark:bg-blue-900': workspace?.id === worksp?.id,
'bg-blue-100 dark:bg-blue-900 hover:bg-blue-200':
workspace?.id === worksp?.id,
}"
@click.prevent="switchWorkspace(worksp)"
>
<div
class="rounded-full h-8 w-8 flex-shrink-0"
role="button"
>
<img
v-if="isUrl(worksp.icon)"
:src="worksp.icon"
:alt="worksp.name + ' icon'"
class="flex-shrink-0 h-8 w-8 rounded-full shadow"
>
<div
v-else
class="rounded-full flex-shrink-0 pt-1 text-xs truncate bg-nt-blue-lighter h-8 w-8 text-center shadow"
v-text="worksp.icon"
/>
</div>
<p class="ml-4 truncate">{{ worksp.name }}</p>
<WorkspaceIcon :workspace="worksp" />
<p class="ml-4 truncate text-sm">{{ worksp.name }}</p>
</a>
</template>
</dropdown>
</div>
</Dropdown>
</template>
<script>
import { computed } from "vue"
import Dropdown from "~/components/global/Dropdown.vue"
import WorkspaceIcon from "~/components/workspaces/WorkspaceIcon.vue"
export default {
name: "WorkspaceDropdown",
components: {
WorkspaceIcon,
Dropdown,
},
@@ -114,14 +89,6 @@ export default {
}
this.formsStore.loadAll(workspace.id)
},
isUrl(str) {
try {
new URL(str)
} catch (_) {
return false
}
return true
},
},
}
</script>