Files
supabase/apps/studio/components/interfaces/Auth/Users/Users.tsx
T
Joshen Lim 17e0c5dbcf Improve snippet organization in SQL Editor (#27881)
* Init changes for sql fodlers

* Added upsert logic in sql-editor-v2 valtio, hooked up with templates and quickstarts

* Do up logic for creating new snippets by typing in /new or by clicking new query button

* Do up logic for updating and deleting snippets

* Do up logic for favourites and shared snippets

* Do up logic for favourites and shared snippets

* Fix

* Fix saving indicator, add empty states for favorites and shared snippets

* Implement sorting

* Some minor QOL improvements

* Minor fix on empty state for private snippets

* Add delete folder mutation

* Implement create and update folder

* Fix reinstate with AI renaming for new snippets under folder

* Support controlled multi select behaviour in private snippets

* Undo changes to tree-view-multi-select

* Support bulk deletes

* Support moving queries + rendering queries in folders

* Support deleting folders and creating a new folder when moving a query

* Fix bug where renaming query removes content

* Add initial loading state in sql editor nav + handle fallback if cannot retrieve content by id

* Fix some spelling

* Fix TS issue in sql folders mutation keys

* Fix toggling favorite

* Lint

* Revert fallback behaviour in ]id] for now

* Fix favorites and shared snippets not showing

* Fix moving currently opened snippet leads to loading

* Support bulk moving

* Improve multi select logic a little

* Nit lint

* Reinstate AI retitling for untitled snippets when running query

* Remove hardcode in useAFlag

* Support creating new snippet in a folder directly

* Fix sharing snippets that are within a folder

* Fix sharing snippets within a folder

* Fix favorite

* Add loading state when fetching folder contents

* Fix favoriting snippets in folders
2024-07-17 12:23:19 +08:00

159 lines
5.1 KiB
TypeScript

import { useQueryClient } from '@tanstack/react-query'
import { useState } from 'react'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import APIDocsButton from 'components/ui/APIDocsButton'
import NoPermission from 'components/ui/NoPermission'
import { authKeys } from 'data/auth/keys'
import { useUsersQuery } from 'data/auth/users-query'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { Search, X } from 'lucide-react'
import { Button, IconRefreshCw, Input, Listbox } from 'ui'
import AddUserDropdown from './AddUserDropdown'
import UsersList from './UsersList'
const Users = () => {
const queryClient = useQueryClient()
const { project } = useProjectContext()
const { ref: projectRef } = useParams()
const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
const [page, setPage] = useState(1)
const [search, setSearch] = useState('')
const [filterKeywords, setFilterKeywords] = useState('')
type Filter = 'verified' | 'unverified' | 'anonymous'
const [filter, setFilter] = useState<Filter>()
const canReadUsers = useCheckPermissions(PermissionAction.TENANT_SQL_SELECT, 'auth.users')
const isPermissionsLoaded = usePermissionsLoaded()
const {
data,
isLoading,
isSuccess,
refetch,
isRefetching,
error,
isPreviousData: isFetchingNextPage,
} = useUsersQuery(
{
projectRef,
page,
keywords: filterKeywords,
filter,
connectionString: project?.connectionString!,
},
{
keepPreviousData: true,
onSuccess(data) {
if (data.users.length <= 0 && data.total > 0) {
queryClient.removeQueries(
authKeys.users(projectRef, { page, keywords: filterKeywords, filter })
)
setPage((prev) => prev - 1)
}
},
}
)
function onVerifiedFilterChange(val: Filter) {
setFilter(val)
}
function clearSearch() {
setSearch('')
setFilterKeywords('')
setFilter(undefined)
}
return (
<div>
<div className="justify-between px-6 pt-6 pb-2 md:flex">
<div className="relative flex space-x-4">
<Input
size="small"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
if (e.keyCode == 13) setFilterKeywords(search)
}}
className="min-w-[275px]"
name="email"
id="email"
placeholder="Search by email or phone number"
icon={<Search size={14} />}
actions={[
search && (
<Button size="tiny" type="text" icon={<X />} onClick={() => clearSearch()} />
),
]}
/>
<Listbox
size="small"
value={filter}
onChange={onVerifiedFilterChange}
name="verified"
id="verified"
className="w-[200px]"
>
<Listbox.Option label="All Users" value="">
All Users
</Listbox.Option>
<Listbox.Option label="Verified Users" value="verified">
Verified Users
</Listbox.Option>
<Listbox.Option label="Un-Verified Users" value="unverified">
Un-Verified Users
</Listbox.Option>
<Listbox.Option label="Anonymous Users" value="anonymous">
Anonymous Users
</Listbox.Option>
</Listbox>
</div>
<div className="mt-4 flex items-center gap-2 md:mt-0">
{isNewAPIDocsEnabled && <APIDocsButton section={['user-management']} />}
<Button
size="tiny"
icon={<IconRefreshCw />}
type="default"
loading={isRefetching && !isFetchingNextPage}
onClick={() => refetch()}
>
Reload
</Button>
<AddUserDropdown projectKpsVersion={project?.kpsVersion} />
</div>
</div>
<section className="thin-scrollbars mt-4 overflow-visible px-6">
<div className="section-block--body relative overflow-x-auto rounded">
<div className="inline-block min-w-full align-middle">
{isPermissionsLoaded && !canReadUsers ? (
<div className="mt-8">
<NoPermission isFullPage resourceText="access your project's users" />
</div>
) : (
<UsersList
page={page}
setPage={setPage}
keywords={filterKeywords}
total={data?.total ?? 0}
users={data?.users ?? []}
isLoading={isLoading}
isSuccess={isSuccess}
isFetchingNextPage={isFetchingNextPage}
error={error}
/>
)}
</div>
</div>
</section>
</div>
)
}
export default Users