diff --git a/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx
new file mode 100644
index 00000000000..29505af9dab
--- /dev/null
+++ b/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx
@@ -0,0 +1,108 @@
+import * as Tooltip from '@radix-ui/react-tooltip'
+import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { checkPermissions } from 'hooks'
+import { IS_PLATFORM } from 'lib/constants'
+import { useState } from 'react'
+import semver from 'semver'
+import { Button, Dropdown, IconMail, IconPlus, IconUserPlus } from 'ui'
+import CreateUserModal from './CreateUserModal'
+import InviteUserModal from './InviteUserModal'
+
+export type AddUserDropdownProps = {
+ projectKpsVersion?: string
+}
+
+const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => {
+ const inviteEnabled = IS_PLATFORM
+ ? semver.gte(
+ // @ts-ignore
+ semver.coerce(projectKpsVersion ?? 'kps-v2.5.4'),
+ semver.coerce('kps-v2.5.3')
+ )
+ : true
+
+ const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user')
+ const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user')
+
+ const [inviteVisible, setInviteVisible] = useState(false)
+ const [createVisible, setCreateVisible] = useState(false)
+
+ return (
+ <>
+
+ {inviteEnabled && (
+
+
+ }
+ disabled={!canInviteUsers}
+ onClick={() => setInviteVisible(true)}
+ >
+ Send Invitation
+
+
+ {!canInviteUsers && (
+
+
+
+
+ You need additional permissions to invite users
+
+
+
+ )}
+
+ )}
+
+
+
+ }
+ disabled={!canCreateUsers}
+ onClick={() => setCreateVisible(true)}
+ >
+ Create New User
+
+
+
+ {!canCreateUsers && (
+
+
+
+
+ You need additional permissions to create users
+
+
+
+ )}
+
+ >
+ }
+ >
+ }>
+ Add User
+
+
+
+ {inviteEnabled && }
+
+ >
+ )
+}
+
+export default AddUserDropdown
diff --git a/studio/components/interfaces/Auth/Users/CreateUserModal.tsx b/studio/components/interfaces/Auth/Users/CreateUserModal.tsx
new file mode 100644
index 00000000000..94698aca740
--- /dev/null
+++ b/studio/components/interfaces/Auth/Users/CreateUserModal.tsx
@@ -0,0 +1,164 @@
+import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { observer } from 'mobx-react-lite'
+import { useContext } from 'react'
+import { Button, Checkbox, Form, IconLock, IconMail, Input, Loading, Modal } from 'ui'
+
+import { useUserCreateMutation } from 'data/auth/user-create-mutation'
+import { useProjectApiQuery } from 'data/config/project-api-query'
+import { checkPermissions, useStore } from 'hooks'
+import { PageContext } from 'pages/project/[ref]/auth/users'
+
+export type CreateUserModalProps = {
+ visible: boolean
+ setVisible: (visible: boolean) => void
+}
+
+const CreateUserModal = ({ visible, setVisible }: CreateUserModalProps) => {
+ const { ui } = useStore()
+ const PageState: any = useContext(PageContext)
+ const projectRef = PageState.projectRef
+
+ const { data, isLoading, isSuccess } = useProjectApiQuery({ projectRef }, { enabled: visible })
+
+ const handleToggle = () => setVisible(!visible)
+ const canCreateUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'create_user')
+
+ const validate = (values: any) => {
+ const errors: any = {}
+ const emailValidateRegex =
+ /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
+
+ if (values.email.length === 0) {
+ errors.email = 'Please enter a valid email'
+ } else if (!emailValidateRegex.test(values.email)) {
+ errors.email = `${values.email} is an invalid email`
+ }
+
+ if (!values.password?.trim()) {
+ errors.password = 'Please enter a password'
+ }
+
+ return errors
+ }
+
+ const { mutateAsync: createUser } = useUserCreateMutation({
+ async onSuccess() {
+ await PageState.fetchData(1)
+ },
+ })
+
+ const onCreateUser = async (values: any, { setSubmitting }: any) => {
+ if (!isSuccess) {
+ ui.setNotification({
+ category: 'error',
+ message: `Failed to create user: Error loading project config`,
+ })
+
+ return
+ }
+
+ const { protocol, endpoint, serviceApiKey } = data.autoApiService
+
+ setSubmitting(true)
+
+ try {
+ await createUser({
+ endpoint,
+ protocol,
+ serviceApiKey,
+ user: values,
+ })
+
+ ui.setNotification({
+ category: 'success',
+ message: `Created user: ${values.email}`,
+ })
+
+ setVisible(false)
+ } catch (error: any) {
+ ui.setNotification({
+ category: 'error',
+ message: `Failed to create user: ${error.message}`,
+ })
+ }
+
+ setSubmitting(false)
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default observer(CreateUserModal)
diff --git a/studio/components/interfaces/Auth/Users/InviteUserModal.tsx b/studio/components/interfaces/Auth/Users/InviteUserModal.tsx
index 5f5898714cf..2ec628eb448 100644
--- a/studio/components/interfaces/Auth/Users/InviteUserModal.tsx
+++ b/studio/components/interfaces/Auth/Users/InviteUserModal.tsx
@@ -1,18 +1,21 @@
-import { useContext, useState } from 'react'
-import { observer } from 'mobx-react-lite'
-import * as Tooltip from '@radix-ui/react-tooltip'
-import { Button, Modal, Input, IconPlus, IconMail, Form } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { observer } from 'mobx-react-lite'
+import { useContext } from 'react'
+import { Button, Form, IconMail, Input, Modal } from 'ui'
-import { useStore, checkPermissions } from 'hooks'
+import { checkPermissions, useStore } from 'hooks'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { PageContext } from 'pages/project/[ref]/auth/users'
-const InviteUserModal = () => {
+export type InviteUserModalProps = {
+ visible: boolean
+ setVisible: (visible: boolean) => void
+}
+
+const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => {
const { ui } = useStore()
const PageState: any = useContext(PageContext)
- const [visible, setVisible] = useState(false)
const handleToggle = () => setVisible(!visible)
const canInviteUsers = checkPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user')
@@ -31,7 +34,7 @@ const InviteUserModal = () => {
return errors
}
- const onInviteuser = async (values: any, { setSubmitting }: any) => {
+ const onInviteUser = async (values: any, { setSubmitting }: any) => {
setSubmitting(true)
const response = await post(`${API_URL}/auth/${PageState.projectRef}/invite`, {
@@ -56,28 +59,6 @@ const InviteUserModal = () => {
return (
-
-
- } disabled={!canInviteUsers}>
- Invite
-
-
- {!canInviteUsers && (
-
-
-
-
- You need additional permissions to invite users
-
-
-
- )}
-
{
onCancel={handleToggle}
>