Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Sean Oliver
Sean Thompson
Sergio Cioban Filho
Shane E
Shreekar Shetty
Sreyas Udayavarman
Stanislav M
Stephen Morgan
Expand Down
113 changes: 77 additions & 36 deletions apps/studio/components/interfaces/Auth/Users/UsersSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import { AuthUsersSearchSubmittedEvent } from 'common/telemetry-constants'
import { Search, X } from 'lucide-react'
import { SetStateAction } from 'react'
import { parseAsString, parseAsStringEnum, useQueryState } from 'nuqs'
import { useState } from 'react'
import {
Button,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectSeparator_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
Select_Shadcn_,
Tooltip,
TooltipContent,
TooltipTrigger,
cn,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'

import { SpecificFilterColumn } from './Users.constants'
import {
PHONE_NUMBER_LEFT_PREFIX_REGEX,
SpecificFilterColumn,
UUIDV4_LEFT_PREFIX_REGEX,
} from './Users.constants'
import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'

const getSearchPlaceholder = (column: SpecificFilterColumn): string => {
switch (column) {
Expand All @@ -20,41 +42,63 @@ const getSearchPlaceholder = (column: SpecificFilterColumn): string => {
}
}

import {
Button,
cn,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectSeparator_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'

interface UsersSearchProps {
search: string
searchInvalid: boolean
specificFilterColumn: SpecificFilterColumn
setSearch: (value: SetStateAction<string>) => void
setFilterKeywords: (value: string) => void
setSpecificFilterColumn: (value: SpecificFilterColumn) => void
improvedSearchEnabled?: boolean
telemetryProps: Omit<AuthUsersSearchSubmittedEvent['properties'], 'trigger'>
telemetryGroups: AuthUsersSearchSubmittedEvent['groups']
onSelectFilterColumn: (value: SpecificFilterColumn) => void
}

export const UsersSearch = ({
search,
searchInvalid,
specificFilterColumn,
setSearch,
setFilterKeywords,
setSpecificFilterColumn,
improvedSearchEnabled = false,
telemetryProps,
telemetryGroups,
onSelectFilterColumn,
}: UsersSearchProps) => {
const [_, setSelectedId] = useQueryState(
'show',
parseAsString.withOptions({ history: 'push', clearOnDefault: true })
)
const [filterKeywords, setFilterKeywords] = useQueryState('keywords', { defaultValue: '' })
const [specificFilterColumn] = useQueryState<SpecificFilterColumn>(
'filter',
parseAsStringEnum<SpecificFilterColumn>([
'id',
'email',
'phone',
'name',
'freeform',
]).withDefault('email')
)

const [search, setSearch] = useState(filterKeywords)
const { mutate: sendEvent } = useSendEventMutation()

const searchInvalid =
!search ||
specificFilterColumn === 'freeform' ||
specificFilterColumn === 'email' ||
specificFilterColumn === 'name'
? false
: specificFilterColumn === 'id'
? !search.match(UUIDV4_LEFT_PREFIX_REGEX)
: !search.match(PHONE_NUMBER_LEFT_PREFIX_REGEX)

const onSubmitSearch = () => {
const s = search.trim().toLocaleLowerCase()
setFilterKeywords(s)
setSelectedId(null)
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'search_input',
...telemetryProps,
keywords: s,
},
groups: telemetryGroups,
})
}

return (
<div className="flex items-center">
<div className="text-xs h-[26px] flex items-center px-1.5 border border-strong rounded-l-md bg-surface-300">
Expand All @@ -63,7 +107,7 @@ export const UsersSearch = ({

<Select_Shadcn_
value={specificFilterColumn}
onValueChange={(v) => setSpecificFilterColumn(v as typeof specificFilterColumn)}
onValueChange={(v) => onSelectFilterColumn(v as typeof specificFilterColumn)}
>
<SelectTrigger_Shadcn_
size="tiny"
Expand Down Expand Up @@ -119,13 +163,10 @@ export const UsersSearch = ({
)}
placeholder={getSearchPlaceholder(specificFilterColumn)}
value={search}
onChange={(e) => {
const value = e.target.value.replace(/\s+/g, '').toLowerCase()
setSearch(value)
}}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
if (!searchInvalid) setFilterKeywords(search.trim().toLocaleLowerCase())
if (!searchInvalid) onSubmitSearch()
}
}}
actions={
Expand Down
76 changes: 24 additions & 52 deletions apps/studio/components/interfaces/Auth/Users/UsersV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,10 @@ import pgMeta from '@supabase/pg-meta'
import type { OptimizedSearchColumns } from '@supabase/pg-meta/src/sql/studio/get-users-types'
import { keepPreviousData, useQueryClient } from '@tanstack/react-query'
import AwesomeDebouncePromise from 'awesome-debounce-promise'
import {
ExternalLinkIcon,
InfoIcon,
RefreshCw,
Trash,
Users,
WandSparklesIcon,
X,
} from 'lucide-react'
import Link from 'next/link'
import { parseAsArrayOf, parseAsString, parseAsStringEnum, useQueryState } from 'nuqs'
import { UIEvent, useEffect, useMemo, useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
import { toast } from 'sonner'

import { LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common'
import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { AlertError } from 'components/ui/AlertError'
import { APIDocsButton } from 'components/ui/APIDocsButton'
import { AlertError } from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { FilterPopover } from 'components/ui/FilterPopover'
import { FormHeader } from 'components/ui/Forms/FormHeader'
Expand All @@ -40,26 +25,41 @@ import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { cleanPointerEventsNoneOnBody, isAtBottom } from 'lib/helpers'
import {
Alert_Shadcn_,
ExternalLinkIcon,
InfoIcon,
RefreshCw,
Trash,
Users,
WandSparklesIcon,
X,
} from 'lucide-react'
import Link from 'next/link'
import { parseAsArrayOf, parseAsString, parseAsStringEnum, useQueryState } from 'nuqs'
import { UIEvent, useEffect, useMemo, useRef, useState } from 'react'
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
import { toast } from 'sonner'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
cn,
LoadingLine,
ResizablePanel,
ResizablePanelGroup,
Select_Shadcn_,
SelectContent_Shadcn_,
SelectGroup_Shadcn_,
SelectItem_Shadcn_,
SelectTrigger_Shadcn_,
SelectValue_Shadcn_,
Select_Shadcn_,
Tooltip,
TooltipContent,
TooltipTrigger,
cn,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'

import { AddUserDropdown } from './AddUserDropdown'
import { DeleteUserModal } from './DeleteUserModal'
import { SortDropdown } from './SortDropdown'
Expand All @@ -69,10 +69,8 @@ import {
ColumnConfiguration,
Filter,
MAX_BULK_DELETE,
PHONE_NUMBER_LEFT_PREFIX_REGEX,
PROVIDER_FILTER_OPTIONS,
USERS_TABLE_COLUMNS,
UUIDV4_LEFT_PREFIX_REGEX,
} from './Users.constants'
import { formatUserColumns, formatUsersData } from './Users.utils'
import { UsersFooter } from './UsersFooter'
Expand Down Expand Up @@ -137,7 +135,7 @@ export const UsersV2 = () => {
'userType',
parseAsStringEnum(['all', 'verified', 'unverified', 'anonymous']).withDefault('all')
)
const [filterKeywords, setFilterKeywords] = useQueryState('keywords', { defaultValue: '' })
const [filterKeywords] = useQueryState('keywords', { defaultValue: '' })
const [sortByValue, setSortByValue] = useQueryState('sortBy', { defaultValue: 'created_at:desc' })
const [sortColumn, sortOrder] = sortByValue.split(':')
const [selectedColumns, setSelectedColumns] = useQueryState(
Expand Down Expand Up @@ -185,7 +183,6 @@ export const UsersV2 = () => {
)

const [columns, setColumns] = useState<Column<any>[]>([])
const [search, setSearch] = useState(filterKeywords)
const [selectedUsers, setSelectedUsers] = useState<Set<any>>(new Set([]))
const [selectedUserToDelete, setSelectedUserToDelete] = useState<User>()
const [showDeleteModal, setShowDeleteModal] = useState(false)
Expand Down Expand Up @@ -330,16 +327,6 @@ export const UsersV2 = () => {
// [Joshen] Only relevant for when selecting one user only
const selectedUserFromCheckbox = users.find((u) => u.id === [...selectedUsers][0])

const searchInvalid =
!search ||
specificFilterColumn === 'freeform' ||
specificFilterColumn === 'email' ||
specificFilterColumn === 'name'
? false
: specificFilterColumn === 'id'
? !search.match(UUIDV4_LEFT_PREFIX_REGEX)
: !search.match(PHONE_NUMBER_LEFT_PREFIX_REGEX)

const telemetryProps = {
sort_column: sortColumn,
sort_order: sortOrder,
Expand Down Expand Up @@ -566,24 +553,10 @@ export const UsersV2 = () => {
<>
<div className="flex flex-wrap items-center gap-2">
<UsersSearch
search={search}
searchInvalid={searchInvalid}
specificFilterColumn={specificFilterColumn}
setSearch={setSearch}
setFilterKeywords={(s) => {
setFilterKeywords(s)
setSelectedId(null)
sendEvent({
action: 'auth_users_search_submitted',
properties: {
trigger: 'search_input',
...telemetryProps,
keywords: s,
},
groups: telemetryGroups,
})
}}
setSpecificFilterColumn={(value) => {
improvedSearchEnabled={improvedSearchEnabled}
telemetryProps={telemetryProps}
telemetryGroups={telemetryGroups}
onSelectFilterColumn={(value) => {
if (value === 'freeform') {
if (isCountWithinThresholdForSortBy) {
updateStorageFilter(value)
Expand All @@ -594,7 +567,6 @@ export const UsersV2 = () => {
updateStorageFilter(value)
}
}}
improvedSearchEnabled={improvedSearchEnabled}
/>

{showUserTypeFilter &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { AnimatePresence, motion } from 'framer-motion'
import { snakeCase } from 'lodash'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'

import { useFlag, useParams } from 'common'
import { CreateAnalyticsBucketSheet } from 'components/interfaces/Storage/AnalyticsBuckets/CreateAnalyticsBucketSheet'
import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query'
Expand All @@ -26,19 +19,26 @@ import { useReplicationSourcesQuery } from 'data/replication/sources-query'
import { useStartPipelineMutation } from 'data/replication/start-pipeline-mutation'
import { useUpdateDestinationPipelineMutation } from 'data/replication/update-destination-pipeline-mutation'
import {
useValidateDestinationMutation,
type ValidationFailure,
useValidateDestinationMutation,
} from 'data/replication/validate-destination-mutation'
import { useValidatePipelineMutation } from 'data/replication/validate-pipeline-mutation'
import { useIcebergNamespaceCreateMutation } from 'data/storage/iceberg-namespace-create-mutation'
import { useS3AccessKeyCreateMutation } from 'data/storage/s3-access-key-create-mutation'
import { AnimatePresence, motion } from 'framer-motion'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { snakeCase } from 'lodash'
import { Loader2 } from 'lucide-react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
PipelineStatusRequestStatus,
usePipelineRequestStatus,
} from 'state/replication-pipeline-request-status'
import { Button, DialogSectionSeparator, Form_Shadcn_, SheetFooter, SheetSection } from 'ui'
import * as z from 'zod'

import { DestinationType } from '../DestinationPanel.types'
import { AdvancedSettings } from './AdvancedSettings'
import { CREATE_NEW_KEY, CREATE_NEW_NAMESPACE } from './DestinationForm.constants'
Expand Down
Loading
Loading