From 045d1aa71dd323753de5552dacae02e118cd1886 Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Fri, 24 May 2024 15:13:34 +0500 Subject: [PATCH 01/16] Add Autocomplete component --- .../tailwind/Autocomplete/index.tsx | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 packages/ui/src/primitives/tailwind/Autocomplete/index.tsx diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx new file mode 100644 index 0000000000..07cc52f6b6 --- /dev/null +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -0,0 +1,91 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { useHookstate } from '@etherealengine/hyperflux' +import React, { useEffect } from 'react' +import Input from '../Input' + +export interface AutoCompleteProps { + className?: string + options: { name: string }[] + placeholder?: string + onSelect: (value: string) => void + value: string +} + +const AutoComplete = ({ options, onSelect, placeholder, className, value }: AutoCompleteProps) => { + const filteredOptions = useHookstate([]) + const showDropdown = useHookstate(false) + const inputValue = useHookstate(value) + + useEffect(() => { + inputValue.set(value) + }, [value]) + + const onChange = (e: React.ChangeEvent) => { + const keyword = e.currentTarget.value + inputValue.set(keyword) + + if (keyword.trim() === '') { + filteredOptions.set([]) + showDropdown.set(false) + return + } + + const filtered = options.filter((option) => option.name.toLowerCase().includes(keyword.toLowerCase())) + + filteredOptions.set(filtered) + showDropdown.set(true) + } + + const handleClick = (option) => { + inputValue.set(option.name) + showDropdown.set(false) + onSelect(option.name) + } + + return ( +
+ + {showDropdown.value && filteredOptions.value.length > 0 && ( +
+
    + {filteredOptions.value.map((option, index) => ( +
  • handleClick(option)} + > + {option.name} +
  • + ))} +
+
+ )} +
+ ) +} + +export default AutoComplete From da0faca1a74c161916216b25680656b505a58edb Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Fri, 24 May 2024 16:21:13 +0500 Subject: [PATCH 02/16] Update Autocomplete dropdown positioning --- packages/ui/src/primitives/tailwind/Autocomplete/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index 07cc52f6b6..52edd9a436 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -70,7 +70,7 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value }: Auto
{showDropdown.value && filteredOptions.value.length > 0 && ( -
+
    {filteredOptions.value.map((option, index) => (
  • Date: Fri, 24 May 2024 17:17:17 +0500 Subject: [PATCH 03/16] Update Autocomplete dropdown width --- packages/ui/src/primitives/tailwind/Autocomplete/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index 52edd9a436..3270ac5317 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -70,7 +70,7 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value }: Auto
    {showDropdown.value && filteredOptions.value.length > 0 && ( -
    +
      {filteredOptions.value.map((option, index) => (
    • Date: Mon, 3 Jun 2024 12:02:32 +0500 Subject: [PATCH 04/16] Refactor AutoComplete component to include onChange prop --- .../tailwind/Autocomplete/index.tsx | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index 3270ac5317..765d105539 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -33,10 +33,11 @@ export interface AutoCompleteProps { placeholder?: string onSelect: (value: string) => void value: string + onChange: (e: React.ChangeEvent) => void } -const AutoComplete = ({ options, onSelect, placeholder, className, value }: AutoCompleteProps) => { - const filteredOptions = useHookstate([]) +const AutoComplete = ({ options, onSelect, placeholder, className, value, onChange }: AutoCompleteProps) => { + const filteredOptions = useHookstate(options) const showDropdown = useHookstate(false) const inputValue = useHookstate(value) @@ -44,38 +45,33 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value }: Auto inputValue.set(value) }, [value]) - const onChange = (e: React.ChangeEvent) => { - const keyword = e.currentTarget.value - inputValue.set(keyword) - - if (keyword.trim() === '') { - filteredOptions.set([]) - showDropdown.set(false) - return - } - - const filtered = options.filter((option) => option.name.toLowerCase().includes(keyword.toLowerCase())) - - filteredOptions.set(filtered) - showDropdown.set(true) - } + useEffect(() => { + const match = options.filter((option) => option.name.toLowerCase().includes(inputValue.value.toLowerCase())) + filteredOptions.set(match) + showDropdown.set(match.length > 0 && inputValue.value !== '') + }, [inputValue.value, options]) const handleClick = (option) => { inputValue.set(option.name) - showDropdown.set(false) onSelect(option.name) + showDropdown.set(false) + } + + const handleInputChange = (event: React.ChangeEvent) => { + inputValue.set(event.target.value) + onChange(event) } return (
      - + {showDropdown.value && filteredOptions.value.length > 0 && ( -
      +
        {filteredOptions.value.map((option, index) => (
      • handleClick(option)} > {option.name} From 87abac439656e745d959adf7a5fcb8cad8ee84b2 Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Mon, 3 Jun 2024 16:08:01 +0500 Subject: [PATCH 05/16] Update Autocomplete options structure --- packages/ui/src/primitives/tailwind/Autocomplete/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index 765d105539..e207d3ff1a 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -29,7 +29,7 @@ import Input from '../Input' export interface AutoCompleteProps { className?: string - options: { name: string }[] + options: { label: string; search: string }[] placeholder?: string onSelect: (value: string) => void value: string @@ -46,7 +46,7 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan }, [value]) useEffect(() => { - const match = options.filter((option) => option.name.toLowerCase().includes(inputValue.value.toLowerCase())) + const match = options.filter((option) => option.search.toLowerCase().includes(inputValue.value.toLowerCase())) filteredOptions.set(match) showDropdown.set(match.length > 0 && inputValue.value !== '') }, [inputValue.value, options]) @@ -74,7 +74,7 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan className="cursor-pointer px-4 py-2 text-theme-secondary" onClick={() => handleClick(option)} > - {option.name} + {option.label}
      • ))}
      From b61927edae850a6e5a3475a6157f9f3b195ee83f Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Mon, 3 Jun 2024 16:36:23 +0500 Subject: [PATCH 06/16] Add useRef hook to track option selection in AutoComplete component --- .../tailwind/Autocomplete/index.tsx | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index e207d3ff1a..fb15dc058a 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ import { useHookstate } from '@etherealengine/hyperflux' -import React, { useEffect } from 'react' +import React, { useEffect, useRef } from 'react' import Input from '../Input' export interface AutoCompleteProps { @@ -40,26 +40,35 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan const filteredOptions = useHookstate(options) const showDropdown = useHookstate(false) const inputValue = useHookstate(value) + const isSelectingOption = useRef(false) useEffect(() => { inputValue.set(value) }, [value]) useEffect(() => { - const match = options.filter((option) => option.search.toLowerCase().includes(inputValue.value.toLowerCase())) - filteredOptions.set(match) - showDropdown.set(match.length > 0 && inputValue.value !== '') + // Only filter and show dropdown if not currently selecting an option + if (!isSelectingOption.current) { + const match = options.filter((option) => option.search.toLowerCase().includes(inputValue.value.toLowerCase())) + filteredOptions.set(match) + showDropdown.set(match.length > 0 && inputValue.value !== '') + } }, [inputValue.value, options]) const handleClick = (option) => { - inputValue.set(option.name) - onSelect(option.name) + isSelectingOption.current = true + inputValue.set(option.label) + onSelect(option.label) showDropdown.set(false) + setTimeout(() => { + isSelectingOption.current = false + }, 1) } const handleInputChange = (event: React.ChangeEvent) => { inputValue.set(event.target.value) onChange(event) + showDropdown.set(true) } return ( From 7bb5ce99b2e8cd18bdedc1bf59543fd78c7d50cf Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Tue, 4 Jun 2024 14:08:24 +0500 Subject: [PATCH 07/16] Refactor Radio component to include a description --- .../src/primitives/tailwind/Radio/index.tsx | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/ui/src/primitives/tailwind/Radio/index.tsx b/packages/ui/src/primitives/tailwind/Radio/index.tsx index 565b8d6dc0..9c3e8abbae 100644 --- a/packages/ui/src/primitives/tailwind/Radio/index.tsx +++ b/packages/ui/src/primitives/tailwind/Radio/index.tsx @@ -32,7 +32,8 @@ export const RadioRoot = ({ onChange, selected, className, - disabled + disabled, + description }: { label: string value: string | number @@ -40,26 +41,30 @@ export const RadioRoot = ({ selected: boolean className?: string disabled?: boolean + description?: string }) => { - const twClassname = twMerge('flex items-center', className) + const twClassname = twMerge('flex flex-col gap-2', className) return (
      - - +
      + + +
      + {description &&
      {description}
      }
      ) } @@ -75,7 +80,7 @@ const Radio = ({ disabled }: { value: T - options: { label: string; value: T }[] + options: { label: string; value: T; description?: string }[] onChange: (value: T) => void className?: string horizontal?: boolean @@ -83,7 +88,7 @@ const Radio = ({ }) => { return (
      - {options.map(({ label, value: optionValue }) => ( + {options.map(({ label, value: optionValue, description }) => ( ({ value={optionValue} onChange={(event) => onChange(event.target.value as T)} disabled={disabled} + description={description} /> ))}
      From d73f2da4ed325088550002baf75e48cbbf5f0314 Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Wed, 5 Jun 2024 12:06:33 +0500 Subject: [PATCH 08/16] Refactor AutoComplete component to remove unused code --- .../primitives/tailwind/Autocomplete/index.tsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index fb15dc058a..8ec9360085 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -29,7 +29,7 @@ import Input from '../Input' export interface AutoCompleteProps { className?: string - options: { label: string; search: string }[] + options: { label: string }[] placeholder?: string onSelect: (value: string) => void value: string @@ -37,7 +37,6 @@ export interface AutoCompleteProps { } const AutoComplete = ({ options, onSelect, placeholder, className, value, onChange }: AutoCompleteProps) => { - const filteredOptions = useHookstate(options) const showDropdown = useHookstate(false) const inputValue = useHookstate(value) const isSelectingOption = useRef(false) @@ -46,15 +45,6 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan inputValue.set(value) }, [value]) - useEffect(() => { - // Only filter and show dropdown if not currently selecting an option - if (!isSelectingOption.current) { - const match = options.filter((option) => option.search.toLowerCase().includes(inputValue.value.toLowerCase())) - filteredOptions.set(match) - showDropdown.set(match.length > 0 && inputValue.value !== '') - } - }, [inputValue.value, options]) - const handleClick = (option) => { isSelectingOption.current = true inputValue.set(option.label) @@ -74,10 +64,10 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan return (
      - {showDropdown.value && filteredOptions.value.length > 0 && ( + {showDropdown.value && options.length > 0 && (
        - {filteredOptions.value.map((option, index) => ( + {options.map((option, index) => (
      • Date: Wed, 5 Jun 2024 13:06:53 +0500 Subject: [PATCH 09/16] Fix Autocomplete dropdown visibility issue --- .../ui/src/primitives/tailwind/Autocomplete/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index 8ec9360085..c889c1cd7b 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -43,6 +43,10 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan useEffect(() => { inputValue.set(value) + + if (!value) { + showDropdown.set(false) + } }, [value]) const handleClick = (option) => { @@ -56,9 +60,10 @@ const AutoComplete = ({ options, onSelect, placeholder, className, value, onChan } const handleInputChange = (event: React.ChangeEvent) => { - inputValue.set(event.target.value) + const value = event.target.value + inputValue.set(value) onChange(event) - showDropdown.set(true) + showDropdown.set(value !== '') } return ( From acb07e14651cf2920463de7c94097b0c2d05bfa7 Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Wed, 5 Jun 2024 14:13:46 +0500 Subject: [PATCH 10/16] Fix project permission type bug --- .../src/projects/project-permission/project-permission.hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts index 2b965e08d3..03b5323362 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts @@ -131,7 +131,7 @@ const checkExistingPermissions = async (context: HookContext Date: Wed, 5 Jun 2024 16:46:07 +0500 Subject: [PATCH 11/16] Fix project permission type in patch --- .../src/projects/project-permission/project-permission.hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts index 03b5323362..be192ce0d5 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts @@ -187,7 +187,7 @@ const ensureTypeInPatch = async (context: HookContext) } const data: ProjectPermissionPatch = context.data as ProjectPermissionPatch - context.data = { type: data.type === 'owner' ? 'owner' : 'editor' } + context.data = { type: data.type === 'owner' ? 'owner' : data.type } } /** From 0ad5dd4b924d16d2efef8a85adbd55e47943f4a6 Mon Sep 17 00:00:00 2001 From: Hurairah Mateen Date: Mon, 10 Jun 2024 15:25:22 +0500 Subject: [PATCH 12/16] IR-2538 Updated project-permission service to include createdBy column (#10346) * Add createdBy column to project-permission service * Update migration name --------- Co-authored-by: Hanzla Mateen --- .../projects/project-permission.schema.ts | 4 + .../20240607053537_createdBy-column.ts | 74 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 packages/server-core/src/projects/project-permission/migrations/20240607053537_createdBy-column.ts diff --git a/packages/common/src/schemas/projects/project-permission.schema.ts b/packages/common/src/schemas/projects/project-permission.schema.ts index 7dc3e12896..33fcb7d856 100644 --- a/packages/common/src/schemas/projects/project-permission.schema.ts +++ b/packages/common/src/schemas/projects/project-permission.schema.ts @@ -47,6 +47,9 @@ export const projectPermissionSchema = Type.Object( userId: TypedString({ format: 'uuid' }), + createdBy: TypedString({ + format: 'uuid' + }), type: Type.String(), user: Type.Ref(userSchema), createdAt: Type.String({ format: 'date-time' }), @@ -86,6 +89,7 @@ export const projectPermissionQueryProperties = Type.Pick(projectPermissionSchem 'id', 'projectId', 'userId', + 'createdBy', 'type' ]) export const projectPermissionQuerySchema = Type.Intersect( diff --git a/packages/server-core/src/projects/project-permission/migrations/20240607053537_createdBy-column.ts b/packages/server-core/src/projects/project-permission/migrations/20240607053537_createdBy-column.ts new file mode 100644 index 0000000000..bfab7c962d --- /dev/null +++ b/packages/server-core/src/projects/project-permission/migrations/20240607053537_createdBy-column.ts @@ -0,0 +1,74 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { projectPermissionPath } from '@etherealengine/common/src/schemas/projects/project-permission.schema' +import type { Knex } from 'knex' + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function up(knex: Knex): Promise { + await knex.raw('SET FOREIGN_KEY_CHECKS=0') + + await addCreatedByColumn(knex, projectPermissionPath) + + await knex.raw('SET FOREIGN_KEY_CHECKS=1') +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function down(knex: Knex): Promise { + await knex.raw('SET FOREIGN_KEY_CHECKS=0') + + await dropCreatedByColumn(knex, projectPermissionPath) + + await knex.raw('SET FOREIGN_KEY_CHECKS=1') +} + +export async function addCreatedByColumn(knex: Knex, tableName: string) { + const createdByColumnExists = await knex.schema.hasColumn(tableName, 'createdBy') + + if (createdByColumnExists === false) { + await knex.schema.alterTable(tableName, async (table) => { + //@ts-ignore + table.uuid('createdBy', 36).collate('utf8mb4_bin').nullable().index() + table.foreign('createdBy').references('id').inTable('user').onDelete('CASCADE').onUpdate('CASCADE') + }) + } +} + +export async function dropCreatedByColumn(knex: Knex, tableName: string) { + const createdByColumnExists = await knex.schema.hasColumn(tableName, 'createdBy') + + if (createdByColumnExists === true) { + await knex.schema.alterTable(tableName, async (table) => { + table.dropForeign('createdBy') + table.dropColumn('createdBy') + }) + } +} From 8afa189f4a583398626e56e7b17559d19784fbb1 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Mon, 10 Jun 2024 17:19:34 +0500 Subject: [PATCH 13/16] Cleaned autocomplete --- .../projects/project-permission.schema.ts | 4 +- .../project-permission.hooks.ts | 15 +++--- .../tailwind/Autocomplete/index.tsx | 46 ++++--------------- 3 files changed, 19 insertions(+), 46 deletions(-) diff --git a/packages/common/src/schemas/projects/project-permission.schema.ts b/packages/common/src/schemas/projects/project-permission.schema.ts index 33fcb7d856..09b7ba0748 100644 --- a/packages/common/src/schemas/projects/project-permission.schema.ts +++ b/packages/common/src/schemas/projects/project-permission.schema.ts @@ -62,7 +62,7 @@ export interface ProjectPermissionType extends Static {} // Schema for creating new entries -export const projectPermissionDataProperties = Type.Partial(projectPermissionSchema) +export const projectPermissionDataProperties = Type.Pick(projectPermissionSchema, ['projectId', 'userId', 'type']) export const projectPermissionDataSchema = Type.Intersect( [ @@ -79,7 +79,7 @@ export const projectPermissionDataSchema = Type.Intersect( export interface ProjectPermissionData extends Static {} // Schema for updating existing entries -export const projectPermissionPatchSchema = Type.Partial(projectPermissionSchema, { +export const projectPermissionPatchSchema = Type.Pick(projectPermissionSchema, ['type'], { $id: 'ProjectPermissionPatch' }) export interface ProjectPermissionPatch extends Static {} diff --git a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts index be192ce0d5..1722814e6d 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts @@ -23,11 +23,6 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { BadRequest, Forbidden } from '@feathersjs/errors' -import { Paginated } from '@feathersjs/feathers' -import { hooks as schemaHooks } from '@feathersjs/schema' -import { disallow, discardQuery, iff, iffElse, isProvider } from 'feathers-hooks-common' - import { INVITE_CODE_REGEX, USER_ID_REGEX } from '@etherealengine/common/src/constants/IdConstants' import { ProjectPermissionData, @@ -40,7 +35,12 @@ import { } from '@etherealengine/common/src/schemas/projects/project-permission.schema' import { projectPath } from '@etherealengine/common/src/schemas/projects/project.schema' import { InviteCode, UserID, UserType, userPath } from '@etherealengine/common/src/schemas/user/user.schema' +import setLoggedInUserData from '@etherealengine/server-core/src/hooks/set-loggedin-user-in-body' import { checkScope } from '@etherealengine/spatial/src/common/functions/checkScope' +import { BadRequest, Forbidden } from '@feathersjs/errors' +import { Paginated } from '@feathersjs/feathers' +import { hooks as schemaHooks } from '@feathersjs/schema' +import { disallow, discardQuery, iff, iffElse, isProvider } from 'feathers-hooks-common' import { HookContext } from '../../../declarations' import logger from '../../ServerLogger' @@ -75,7 +75,7 @@ const ensureInviteCode = async (context: HookContext) } if (data[0].userId && INVITE_CODE_REGEX.test(data[0].userId)) { data[0].inviteCode = data[0].userId as string as InviteCode - delete data[0].userId + delete (data[0] as any).userId } context.data = data[0] } @@ -187,7 +187,7 @@ const ensureTypeInPatch = async (context: HookContext) } const data: ProjectPermissionPatch = context.data as ProjectPermissionPatch - context.data = { type: data.type === 'owner' ? 'owner' : data.type } + context.data = { type: data.type === 'owner' ? 'owner' : data.type } as any } /** @@ -224,6 +224,7 @@ export default { iff(isProvider('external'), verifyProjectOwner()), () => schemaHooks.validateData(projectPermissionDataValidator), schemaHooks.resolveData(projectPermissionDataResolver), + setLoggedInUserData('createdBy'), ensureInviteCode, checkExistingPermissions ], diff --git a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx index c889c1cd7b..711e4188cc 100644 --- a/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx +++ b/packages/ui/src/primitives/tailwind/Autocomplete/index.tsx @@ -23,60 +23,32 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { useHookstate } from '@etherealengine/hyperflux' -import React, { useEffect, useRef } from 'react' +import React from 'react' import Input from '../Input' +export type AutoCompleteOptionsType = { label: string; value: any } + export interface AutoCompleteProps { + value: string + options: AutoCompleteOptionsType[] className?: string - options: { label: string }[] placeholder?: string - onSelect: (value: string) => void - value: string + onSelect: (value: any) => void onChange: (e: React.ChangeEvent) => void } const AutoComplete = ({ options, onSelect, placeholder, className, value, onChange }: AutoCompleteProps) => { - const showDropdown = useHookstate(false) - const inputValue = useHookstate(value) - const isSelectingOption = useRef(false) - - useEffect(() => { - inputValue.set(value) - - if (!value) { - showDropdown.set(false) - } - }, [value]) - - const handleClick = (option) => { - isSelectingOption.current = true - inputValue.set(option.label) - onSelect(option.label) - showDropdown.set(false) - setTimeout(() => { - isSelectingOption.current = false - }, 1) - } - - const handleInputChange = (event: React.ChangeEvent) => { - const value = event.target.value - inputValue.set(value) - onChange(event) - showDropdown.set(value !== '') - } - return (
        - - {showDropdown.value && options.length > 0 && ( + + {options.length > 0 && (
          {options.map((option, index) => (
        • handleClick(option)} + onClick={() => onSelect(option.value)} > {option.label}
        • From f8cc37c1e0afac9dfeb8c1af0bb309642afb63b3 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Mon, 10 Jun 2024 17:24:34 +0500 Subject: [PATCH 14/16] feat: Refactor project-permission.hooks.ts to use explicit type casting --- .../src/projects/project-permission/project-permission.hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts index 1722814e6d..3fc1c83f09 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.hooks.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.hooks.ts @@ -187,7 +187,7 @@ const ensureTypeInPatch = async (context: HookContext) } const data: ProjectPermissionPatch = context.data as ProjectPermissionPatch - context.data = { type: data.type === 'owner' ? 'owner' : data.type } as any + context.data = { type: data.type === 'owner' ? 'owner' : data.type } as ProjectPermissionData } /** From 93ccdba14ee8fe5b6d8d213c98fddedb02669606 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Mon, 10 Jun 2024 17:37:00 +0500 Subject: [PATCH 15/16] Updated tests --- .../project-permission.test.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/server-core/src/projects/project-permission/project-permission.test.ts b/packages/server-core/src/projects/project-permission/project-permission.test.ts index 3a0320d27c..93f719d9a2 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.test.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.test.ts @@ -196,7 +196,8 @@ describe('project-permission.test', () => { project1Permission2 = await app.service(projectPermissionPath).create( { projectId: project1.id, - userId: user2.id + userId: user2.id, + type: 'editor' }, params ) @@ -216,7 +217,8 @@ describe('project-permission.test', () => { const duplicate = await app.service(projectPermissionPath).create( { projectId: project1.id, - userId: user2.id + userId: user2.id, + type: 'editor' }, params ) @@ -237,7 +239,8 @@ describe('project-permission.test', () => { await app.service(projectPermissionPath).create( { projectId: 'abcdefg', - userId: user2.id + userId: user2.id, + type: 'editor' }, params ) @@ -259,7 +262,8 @@ describe('project-permission.test', () => { await app.service(projectPermissionPath).create( { projectId: project1.id, - userId: 'abcdefg' as UserID + userId: 'abcdefg' as UserID, + type: 'editor' }, params ) @@ -281,7 +285,8 @@ describe('project-permission.test', () => { const res = await app.service(projectPermissionPath).create( { projectId: project1.id, - userId: user3.id + userId: user3.id, + type: 'editor' }, params ) @@ -303,7 +308,8 @@ describe('project-permission.test', () => { await app.service(projectPermissionPath).create( { projectId: project1.id, - userId: user3.id + userId: user3.id, + type: 'editor' }, params ) @@ -353,8 +359,6 @@ describe('project-permission.test', () => { const update = (await app.service(projectPermissionPath).patch( project1Permission2.id, { - projectId: project1.id, - userId: 'abcdefg' as UserID, type: 'owner' } // params @@ -374,8 +378,6 @@ describe('project-permission.test', () => { const update = (await app.service(projectPermissionPath).patch( project1Permission2.id, { - projectId: project1.id, - userId: user2.id, type: 'editor' }, params @@ -397,8 +399,6 @@ describe('project-permission.test', () => { await app.service(projectPermissionPath).patch( project1Permission2.id, { - projectId: project1.id, - userId: user3.id, type: '' }, params @@ -427,8 +427,6 @@ describe('project-permission.test', () => { await app.service(projectPermissionPath).patch( project1Permission2.id, { - projectId: project1.id, - userId: user3.id, type: '' }, params From 501487e5e1b64f5e690519385bc4d51cd11942c3 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Mon, 10 Jun 2024 17:46:45 +0500 Subject: [PATCH 16/16] feat: Add 'type' parameter to createPermission function in ProjectService --- .../components/project/ManageUserPermissionModal.tsx | 2 +- .../client-core/src/common/services/ProjectService.ts | 9 ++++++--- packages/editor/src/components/projects/ProjectsPage.tsx | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx b/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx index 9e6d8b3453..286eac578b 100644 --- a/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx +++ b/packages/client-core/src/admin/components/project/ManageUserPermissionModal.tsx @@ -64,7 +64,7 @@ export default function ManageUserPermissionModal({ return } try { - await ProjectService.createPermission(userInviteCode.value, project.id) + await ProjectService.createPermission(userInviteCode.value, project.id, 'reviewer') } catch (err) { NotificationService.dispatchNotify(err.message, { variant: 'error' }) } diff --git a/packages/client-core/src/common/services/ProjectService.ts b/packages/client-core/src/common/services/ProjectService.ts index abe046f1a4..909bebc95a 100644 --- a/packages/client-core/src/common/services/ProjectService.ts +++ b/packages/client-core/src/common/services/ProjectService.ts @@ -45,7 +45,8 @@ import { projectPath, projectPermissionPath, ProjectType, - ProjectUpdateParams + ProjectUpdateParams, + UserID } from '@etherealengine/common/src/schema.type.module' import { Engine } from '@etherealengine/ecs/src/Engine' import { defineState, getMutableState, useHookstate } from '@etherealengine/hyperflux' @@ -176,11 +177,13 @@ export const ProjectService = { } }, - createPermission: async (userInviteCode: InviteCode, projectId: string) => { + createPermission: async (userInviteCode: InviteCode, projectId: string, type: string) => { try { return Engine.instance.api.service(projectPermissionPath).create({ inviteCode: userInviteCode, - projectId: projectId + userId: '' as UserID, + projectId: projectId, + type }) } catch (err) { logger.error('Error with creating new project-permission', err) diff --git a/packages/editor/src/components/projects/ProjectsPage.tsx b/packages/editor/src/components/projects/ProjectsPage.tsx index 08abc2f5ba..40270dcf6f 100644 --- a/packages/editor/src/components/projects/ProjectsPage.tsx +++ b/packages/editor/src/components/projects/ProjectsPage.tsx @@ -270,7 +270,7 @@ const ProjectsPage = ({ studioPath }: { studioPath: string }) => { } const onCreatePermission = async (userInviteCode: InviteCode, projectId: string) => { - await ProjectService.createPermission(userInviteCode, projectId) + await ProjectService.createPermission(userInviteCode, projectId, 'reviewer') } const onPatchPermission = async (id: string, type: string) => {