mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
update collection form (#29791)
* add component for collection form * use new comp in create collection * update updatecollection query * add comp to update collection dialog * add missing ttype
This commit is contained in:
1 parent
b323f1d5b2
commit
c190e04456
4 files changed
+189
-226
No files matched your search
@@ -0,0 +1,152 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormMessage_Shadcn_,
|
||||
Form_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
|
||||
import { Admonition } from 'ui-patterns'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
|
||||
export interface CollectionFormValues {
|
||||
name: string
|
||||
retention_days: number
|
||||
}
|
||||
|
||||
interface CollectionFormProps {
|
||||
initialValues?: Partial<CollectionFormValues>
|
||||
onSubmit: (values: CollectionFormValues) => void
|
||||
isLoading?: boolean
|
||||
onCancelClick: () => void
|
||||
}
|
||||
|
||||
export const CollectionForm = ({
|
||||
initialValues,
|
||||
onSubmit,
|
||||
isLoading,
|
||||
onCancelClick,
|
||||
}: CollectionFormProps) => {
|
||||
const { plan, isLoading: planLoading } = useCurrentOrgPlan()
|
||||
const router = useRouter()
|
||||
const currentOrg = useSelectedOrganization()
|
||||
|
||||
function getMaxRetentionDays() {
|
||||
if (planLoading) return 1
|
||||
if (plan?.id === 'free') return 3
|
||||
else return 90
|
||||
}
|
||||
const maxRetentionDays = getMaxRetentionDays()
|
||||
|
||||
const FormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
retention_days: z.coerce.number().min(1).max(maxRetentionDays).multipleOf(1).positive().int(),
|
||||
})
|
||||
|
||||
const form = useForm<CollectionFormValues>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
name: initialValues?.name || '',
|
||||
retention_days: initialValues?.retention_days || maxRetentionDays,
|
||||
},
|
||||
})
|
||||
|
||||
const handleSubmit = form.handleSubmit(onSubmit)
|
||||
|
||||
const formatDays = (days: number) => {
|
||||
return days === 1 ? `1 day` : `${days} days`
|
||||
}
|
||||
|
||||
const retentionDescription = (days: number) => {
|
||||
if (!Boolean(days) || days < 1)
|
||||
return <>Your plan allows for a maximum of {formatDays(maxRetentionDays)}.</>
|
||||
return (
|
||||
<>
|
||||
Your logs will be removed after {formatDays(days)}. <br />
|
||||
Your plan allows for a maximum of {formatDays(maxRetentionDays)}.
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Modal.Content className="space-y-3">
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Collection name" layout="horizontal">
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ autoFocus placeholder="Events" {...field} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="retention_days"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label="Retention"
|
||||
layout="horizontal"
|
||||
description={retentionDescription(Number(field.value))}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Input_Shadcn_ type="number" {...field} />
|
||||
<span className="absolute right-3 text-foreground-lighter text-sm pointer-events-none">
|
||||
days
|
||||
</span>
|
||||
</div>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</Modal.Content>
|
||||
|
||||
{plan?.id === 'free' && (
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Admonition
|
||||
className="border-none bg-transparent"
|
||||
type="default"
|
||||
title="Upgrade your plan to increase retention"
|
||||
description={
|
||||
<div>
|
||||
Upgrade to the pro plan to increase retention to 90 days.
|
||||
<Button
|
||||
className="mt-2"
|
||||
type="primary"
|
||||
onClick={() =>
|
||||
router.push(`/org/${currentOrg?.slug}/billing?panel=subscriptionPlan`)
|
||||
}
|
||||
>
|
||||
Upgrade plan
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FormMessage_Shadcn_ />
|
||||
<Modal.Content className="py-4 border-t flex items-center justify-end gap-2">
|
||||
<Button size="tiny" type="default" onClick={onCancelClick}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="tiny" loading={isLoading} disabled={isLoading} htmlType="submit">
|
||||
Create collection
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +1,10 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useCreateCollection } from 'data/analytics/warehouse-collections-create-mutation'
|
||||
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
|
||||
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
||||
import {
|
||||
Button,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormMessage_Shadcn_,
|
||||
Form_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
import { Admonition } from 'ui-patterns/admonition'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import { Modal } from 'ui'
|
||||
import { CollectionForm } from './CollectionForm'
|
||||
|
||||
export const CreateWarehouseCollectionModal = ({
|
||||
open,
|
||||
@@ -30,8 +15,6 @@ export const CreateWarehouseCollectionModal = ({
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { ref } = useParams()
|
||||
const { plan, isLoading: planLoading } = useCurrentOrgPlan()
|
||||
const currentOrg = useSelectedOrganization()
|
||||
|
||||
const { mutate: createCollection, isLoading } = useCreateCollection({
|
||||
onSuccess: (data) => {
|
||||
@@ -44,64 +27,6 @@ export const CreateWarehouseCollectionModal = ({
|
||||
},
|
||||
})
|
||||
|
||||
function getMaxRetentionDays() {
|
||||
if (planLoading) return 1
|
||||
if (plan?.id === 'free') return 3
|
||||
else return 90
|
||||
}
|
||||
const FormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
retention_days: z.coerce
|
||||
.number()
|
||||
.min(1)
|
||||
.max(getMaxRetentionDays())
|
||||
.multipleOf(1)
|
||||
.positive()
|
||||
.int(),
|
||||
})
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
retention_days: getMaxRetentionDays(),
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
form.reset()
|
||||
}
|
||||
}, [open, form])
|
||||
|
||||
const onSubmit = form.handleSubmit(async (vals) => {
|
||||
if (!ref) {
|
||||
toast.error('Project ref not found')
|
||||
return
|
||||
}
|
||||
createCollection({
|
||||
projectRef: ref,
|
||||
name: vals.name,
|
||||
retention_days: vals.retention_days,
|
||||
})
|
||||
})
|
||||
|
||||
const formatDays = (days: number) => {
|
||||
if (days === 1) return `1 day`
|
||||
return `${days} days`
|
||||
}
|
||||
const retentionDescription = (days: number) => {
|
||||
const maxRetentionDays = getMaxRetentionDays()
|
||||
if (planLoading) return ''
|
||||
if (!Boolean(days) || days < 1)
|
||||
return <>Your plan allows for a maximum of {formatDays(maxRetentionDays)}.</>
|
||||
return (
|
||||
<>
|
||||
Your logs will be removed after {formatDays(days)}. <br />
|
||||
Your plan allows for a maximum of {formatDays(maxRetentionDays)}.
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="medium"
|
||||
@@ -110,84 +35,20 @@ export const CreateWarehouseCollectionModal = ({
|
||||
visible={open}
|
||||
hideFooter
|
||||
>
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content className="py-4">
|
||||
<p className="pb-5 text-foreground-light text-sm">
|
||||
An event collection stores time-based data and related information in Supabase's
|
||||
analytics system. You can use SQL to analyze this data without affecting the
|
||||
performance of your main database operations.
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Collection name" layout="horizontal">
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ autoFocus placeholder="Events" {...field} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="retention_days"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
label="Retention"
|
||||
layout="horizontal"
|
||||
description={retentionDescription(Number(field.value))}
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Input_Shadcn_ type="number" {...field} />
|
||||
<span className="absolute right-3 text-foreground-lighter text-sm pointer-events-none">
|
||||
days
|
||||
</span>
|
||||
</div>
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormMessage_Shadcn_ />
|
||||
</Modal.Content>
|
||||
|
||||
{plan?.id === 'free' && (
|
||||
<>
|
||||
<Modal.Separator />
|
||||
<Admonition
|
||||
className="border-none bg-transparent"
|
||||
type="default"
|
||||
title="Upgrade your plan to increase retention"
|
||||
description={
|
||||
<div>
|
||||
Upgrade to the pro plan to increase retention to 90 days.
|
||||
<Button
|
||||
className="mt-2"
|
||||
type="primary"
|
||||
onClick={() =>
|
||||
router.push(`/org/${currentOrg?.slug}/billing?panel=subscriptionPlan`)
|
||||
}
|
||||
>
|
||||
Upgrade plan
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Modal.Content className="py-4 border-t flex items-center justify-end gap-2">
|
||||
<Button size="tiny" type="default" onClick={() => onOpenChange(!open)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="tiny" loading={isLoading} disabled={isLoading} htmlType="submit">
|
||||
Create collection
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
<CollectionForm
|
||||
onCancelClick={() => onOpenChange(false)}
|
||||
isLoading={isLoading}
|
||||
onSubmit={(vals) => {
|
||||
if (!ref) {
|
||||
return
|
||||
}
|
||||
createCollection({
|
||||
projectRef: ref,
|
||||
name: vals.name,
|
||||
retention_days: vals.retention_days,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +1,32 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { Database, EditIcon, MoreHorizontal, TrashIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Database, EditIcon, TrashIcon } from 'lucide-react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { FormMessage } from '@ui/components/shadcn/ui/form'
|
||||
import { useParams } from 'common'
|
||||
import { useDeleteCollectionMutation } from 'data/analytics/warehouse-collections-delete-mutation'
|
||||
import { useUpdateCollection } from 'data/analytics/warehouse-collections-update-mutation'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
FormControl_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
Form_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Modal,
|
||||
TooltipContent_Shadcn_,
|
||||
TooltipTrigger_Shadcn_,
|
||||
Tooltip_Shadcn_,
|
||||
cn,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
import { LogsSidebarItem } from '../Settings/Logs/SidebarV2/SidebarItem'
|
||||
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
||||
import { CollectionForm } from './CollectionForm'
|
||||
|
||||
type Props = {
|
||||
item: {
|
||||
id: number
|
||||
token: string
|
||||
name: string
|
||||
retention_days: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,16 +59,6 @@ export const WarehouseMenuItem = ({ item }: Props) => {
|
||||
|
||||
const isLoading = updateCollection.isLoading || deleteCollection.isLoading
|
||||
|
||||
const UpdateFormSchema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: 'Collection name is required',
|
||||
}),
|
||||
})
|
||||
|
||||
const updateForm = useForm<z.infer<typeof UpdateFormSchema>>({
|
||||
resolver: zodResolver(UpdateFormSchema),
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<LogsSidebarItem
|
||||
@@ -100,12 +78,12 @@ export const WarehouseMenuItem = ({ item }: Props) => {
|
||||
}}
|
||||
>
|
||||
<EditIcon className="mr-2" size={14} />
|
||||
<div>Rename collection</div>
|
||||
<div>Update collection</div>
|
||||
</DropdownMenuItem>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
{!canUpdateCollection && (
|
||||
<TooltipContent_Shadcn_ side="right">
|
||||
You need additional permissions to rename collections
|
||||
You need additional permissions to update collections
|
||||
</TooltipContent_Shadcn_>
|
||||
)}
|
||||
</Tooltip_Shadcn_>
|
||||
@@ -140,48 +118,19 @@ export const WarehouseMenuItem = ({ item }: Props) => {
|
||||
hideFooter
|
||||
header={`Update collection "${item.name}"`}
|
||||
>
|
||||
<Form_Shadcn_ {...updateForm}>
|
||||
<form
|
||||
id="update-collection-form"
|
||||
onSubmit={updateForm.handleSubmit((data) =>
|
||||
updateCollection.mutate({
|
||||
projectRef,
|
||||
collectionToken: item.token,
|
||||
name: data.name,
|
||||
})
|
||||
)}
|
||||
>
|
||||
<Modal.Content className="space-y-6 py-6">
|
||||
<FormField_Shadcn_
|
||||
control={updateForm.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout label="Collection name">
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ defaultValue={item.name} type="text" {...field} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<FormMessage />
|
||||
<div className="flex gap-2 justify-end p-3 border-t">
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
type="outline"
|
||||
onClick={() => {
|
||||
setShowUpdateDialog(false)
|
||||
}}
|
||||
htmlType="reset"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isLoading}>
|
||||
Update collection
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
<CollectionForm
|
||||
onCancelClick={() => setShowUpdateDialog(false)}
|
||||
isLoading={isLoading}
|
||||
initialValues={{ name: item.name, retention_days: item.retention_days }}
|
||||
onSubmit={({ name, retention_days }) => {
|
||||
updateCollection.mutate({
|
||||
projectRef,
|
||||
collectionToken: item.token,
|
||||
name,
|
||||
retention_days,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<ConfirmationModal
|
||||
variant="destructive"
|
||||
|
||||
@@ -13,6 +13,7 @@ export type UpdateCollectionArgs = {
|
||||
export type UpdateCollectionPayload = {
|
||||
name?: string
|
||||
// description?: string
|
||||
retention_days?: number
|
||||
}
|
||||
|
||||
export async function updateCollection(
|
||||
@@ -51,7 +52,7 @@ export const useUpdateCollection = ({
|
||||
(payload) =>
|
||||
updateCollection(
|
||||
{ projectRef: payload.projectRef, collectionToken: payload.collectionToken },
|
||||
{ name: payload.name }
|
||||
{ name: payload.name, retention_days: payload.retention_days }
|
||||
),
|
||||
{
|
||||
async onSuccess(data, variables, context) {
|
||||
|
||||
Reference in new issue
Block a user