mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
[Dashboard] Chore: update Modal component to use new components (#26844)
* init * add more examples * update modal * update modals * more modals updated * update modal * Update NewAccessTokenButton.tsx * Update NewAccessTokenButton.tsx * fix: issue with scale on dialog opening. chore: added bg-dialog color * fix modals * add deprecation notices * revert bg * revert * Update Modal.tsx * Update PITRSelection.tsx * update dialog docs * fix: issue with payment method modal * update terminal instructions * update padding defaults
This commit is contained in:
1 parent
21890675e5
commit
972773ca87
95 files changed
+2375
-2194
No files matched your search
@@ -1754,6 +1754,50 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"modal-demo": {
|
||||
name: "modal-demo",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/modal-demo")),
|
||||
source: "",
|
||||
files: ["registry/default/example/modal-demo.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"modal-aligned-footer": {
|
||||
name: "modal-aligned-footer",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/modal-aligned-footer")),
|
||||
source: "",
|
||||
files: ["registry/default/example/modal-aligned-footer.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"modal-custom-footer": {
|
||||
name: "modal-custom-footer",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/modal-custom-footer")),
|
||||
source: "",
|
||||
files: ["registry/default/example/modal-custom-footer.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"modal-hide-footer": {
|
||||
name: "modal-hide-footer",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/modal-hide-footer")),
|
||||
source: "",
|
||||
files: ["registry/default/example/modal-hide-footer.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"color-usage-surface-www-and-docs": {
|
||||
name: "color-usage-surface-www-and-docs",
|
||||
type: "docs:example",
|
||||
|
||||
@@ -82,6 +82,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/admonition',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Modal',
|
||||
href: '/docs/fragments/modal',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Text Confirm Dialog',
|
||||
href: '/docs/fragments/text-confirm-dialog',
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Modal
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
fragment: true
|
||||
links:
|
||||
doc: https://www.radix-ui.com/docs/primitives/components/dialog
|
||||
api: https://www.radix-ui.com/docs/primitives/components/dialog#api-reference
|
||||
---
|
||||
|
||||
<ComponentPreview name="modal-demo" peekCode wide />
|
||||
|
||||
## Examples
|
||||
|
||||
### Aligned footer
|
||||
|
||||
<ComponentPreview name="modal-aligned-footer" />
|
||||
|
||||
### Hide footer
|
||||
|
||||
<ComponentPreview name="modal-hide-footer" />
|
||||
|
||||
### Custom footer
|
||||
|
||||
<ComponentPreview name="modal-custom-footer" />
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Copy } from 'lucide-react'
|
||||
|
||||
import { Button } from 'ui'
|
||||
import { Button, DialogSection, DialogSectionSeparator } from 'ui'
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -18,33 +18,36 @@ export default function DialogCloseButton() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="outline">Share</Button>
|
||||
<Button type="default">Share</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogHeader padding={'small'}>
|
||||
<DialogTitle>Share link</DialogTitle>
|
||||
<DialogDescription>Anyone who has this link will be able to view this.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="grid flex-1 gap-2">
|
||||
<Label_Shadcn_ htmlFor="link" className="sr-only">
|
||||
Link
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_
|
||||
id="link"
|
||||
defaultValue="https://ui.shadcn.com/docs/installation"
|
||||
readOnly
|
||||
/>
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection padding={'small'}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="grid flex-1 gap-2">
|
||||
<Label_Shadcn_ htmlFor="link" className="sr-only">
|
||||
Link
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_
|
||||
id="link"
|
||||
defaultValue="https://ui.shadcn.com/docs/installation"
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<Button htmlType="submit" size="small" type="secondary" className="px-3">
|
||||
<span className="sr-only">Copy</span>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button htmlType="submit" size="small" type="secondary" className="px-3">
|
||||
<span className="sr-only">Copy</span>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogFooter className="sm:justify-start">
|
||||
</DialogSection>
|
||||
<DialogFooter className="sm:justify-start" padding={'small'}>
|
||||
<DialogClose asChild>
|
||||
<Button htmlType="button" type="secondary">
|
||||
Close
|
||||
<Button type="default" htmlType="button">
|
||||
Custom Close Button
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button } from 'ui'
|
||||
import { Button, DialogSection, DialogSectionSeparator } from 'ui'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -15,30 +15,31 @@ export default function DialogDemo() {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="outline">Edit Profile</Button>
|
||||
<Button type="default">Edit Profile</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogHeader padding={'small'}>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make changes to your profile here. Click save when you're done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<DialogSectionSeparator />
|
||||
<DialogSection className="space-y-4" padding={'small'}>
|
||||
<div>
|
||||
<Label_Shadcn_ htmlFor="name" className="text-right">
|
||||
Name
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_ id="name" defaultValue="Pedro Duarte" className="col-span-3" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<div>
|
||||
<Label_Shadcn_ htmlFor="username" className="text-right">
|
||||
Username
|
||||
</Label_Shadcn_>
|
||||
<Input_Shadcn_ id="username" defaultValue="@peduarte" className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
</DialogSection>
|
||||
<DialogFooter padding={'small'}>
|
||||
<Button htmlType="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalAlignedFooter() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
alignFooter="right"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Badge, Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalCustomFooter() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
customFooter={
|
||||
<div className="flex items-center gap-3">
|
||||
<div>
|
||||
<Badge variant="destructive" dot size="small">
|
||||
Proceed with caution
|
||||
</Badge>
|
||||
</div>
|
||||
<Button type="secondary">Cancel</Button>
|
||||
<Button type="danger">Delete</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalDemo() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalHideFooter() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
hideFooter={true}
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Link2 } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
export default function ModalVerticalLayout() {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button type="default" onClick={() => setVisible(!visible)}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(!visible)}
|
||||
onConfirm={() => setVisible(!visible)}
|
||||
title="This is the title of the modal"
|
||||
description="And i am the description"
|
||||
layout="vertical"
|
||||
size="medium"
|
||||
hideClose={false}
|
||||
header={
|
||||
<div className="flex items-center gap-2 text-foreground">
|
||||
<div className="text-brand">
|
||||
<Link2 />
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h3>This is the title</h3>
|
||||
<span className="text-xs text-foreground-muted">This is the title</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
Modal content is inserted here, if you need to insert anything into the Modal you can do
|
||||
so via `children`.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1012,6 +1012,26 @@ export const examples: Registry = [
|
||||
type: 'components:example',
|
||||
files: ['example/tree-view-multi-select.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'modal-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/modal-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'modal-aligned-footer',
|
||||
type: 'components:example',
|
||||
files: ['example/modal-aligned-footer.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'modal-custom-footer',
|
||||
type: 'components:example',
|
||||
files: ['example/modal-custom-footer.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'modal-hide-footer',
|
||||
type: 'components:example',
|
||||
files: ['example/modal-hide-footer.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'color-usage-surface-www-and-docs',
|
||||
type: 'docs:example',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useState } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
DialogFooter,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -87,7 +88,7 @@ const NewAccessTokenButton = ({ onCreateToken }: NewAccessTokenButtonProps) => {
|
||||
<Modal
|
||||
closable
|
||||
hideFooter
|
||||
size="medium"
|
||||
size="small"
|
||||
visible={isOpen}
|
||||
onCancel={() => setIsOpen(!isOpen)}
|
||||
header={
|
||||
@@ -105,7 +106,7 @@ const NewAccessTokenButton = ({ onCreateToken }: NewAccessTokenButtonProps) => {
|
||||
validate={validate}
|
||||
>
|
||||
{() => (
|
||||
<div className="py-3 space-y-4">
|
||||
<>
|
||||
{tokenScope === 'V0' && (
|
||||
<Modal.Content>
|
||||
<Alert
|
||||
@@ -139,8 +140,7 @@ const NewAccessTokenButton = ({ onCreateToken }: NewAccessTokenButtonProps) => {
|
||||
className="w-full"
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<DialogFooter>
|
||||
<div className="flex items-center space-x-2 justify-end">
|
||||
<Button type="default" disabled={isLoading} onClick={() => setIsOpen(false)}>
|
||||
Cancel
|
||||
@@ -149,8 +149,8 @@ const NewAccessTokenButton = ({ onCreateToken }: NewAccessTokenButtonProps) => {
|
||||
Generate token
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -87,7 +87,7 @@ const FeaturePreviewModal = () => {
|
||||
onCancel={handleCloseFeaturePreviewModal}
|
||||
>
|
||||
{FEATURE_PREVIEWS.length > 0 ? (
|
||||
<div className="flex border-t">
|
||||
<div className="flex">
|
||||
<div>
|
||||
<ScrollArea className="h-[550px] w-[280px] border-r">
|
||||
{FEATURE_PREVIEWS.map((feature) => {
|
||||
|
||||
@@ -33,45 +33,43 @@ const PolicyEditor = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="space-y-8 py-8">
|
||||
<Modal.Content>
|
||||
<PolicyName
|
||||
name={policyFormFields.name}
|
||||
limit={63}
|
||||
onUpdatePolicyName={(name) => onUpdatePolicyFormFields({ name })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
{isNewPolicy && (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<PolicyAllowedOperation
|
||||
operation={operation}
|
||||
onSelectOperation={(command) => onUpdatePolicyFormFields({ command })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
</>
|
||||
)}
|
||||
<Modal.Content>
|
||||
<PolicyRoles
|
||||
selectedRoles={selectedRoles}
|
||||
onUpdateSelectedRoles={(roles) => onUpdatePolicyFormFields({ roles })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<PolicyDefinition
|
||||
operation={operation}
|
||||
definition={definition}
|
||||
check={check}
|
||||
onUpdatePolicyUsing={(definition: string | undefined) =>
|
||||
onUpdatePolicyFormFields({ definition })
|
||||
}
|
||||
onUpdatePolicyCheck={(check: string | undefined) => onUpdatePolicyFormFields({ check })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<PolicyName
|
||||
name={policyFormFields.name}
|
||||
limit={63}
|
||||
onUpdatePolicyName={(name) => onUpdatePolicyFormFields({ name })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
{isNewPolicy && (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<PolicyAllowedOperation
|
||||
operation={operation}
|
||||
onSelectOperation={(command) => onUpdatePolicyFormFields({ command })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
</>
|
||||
)}
|
||||
<Modal.Content>
|
||||
<PolicyRoles
|
||||
selectedRoles={selectedRoles}
|
||||
onUpdateSelectedRoles={(roles) => onUpdatePolicyFormFields({ roles })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<PolicyDefinition
|
||||
operation={operation}
|
||||
definition={definition}
|
||||
check={check}
|
||||
onUpdatePolicyUsing={(definition: string | undefined) =>
|
||||
onUpdatePolicyFormFields({ definition })
|
||||
}
|
||||
onUpdatePolicyCheck={(check: string | undefined) => onUpdatePolicyFormFields({ check })}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<PolicyEditorFooter
|
||||
showTemplates={isNewPolicy}
|
||||
onViewTemplates={onViewTemplates}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
DialogSectionSeparator,
|
||||
Form,
|
||||
IconAlertCircle,
|
||||
IconExternalLink,
|
||||
@@ -173,7 +174,8 @@ const RedirectUrls = () => {
|
||||
size="small"
|
||||
visible={open}
|
||||
onCancel={() => setOpen(!open)}
|
||||
header={<h3 className="text-sm">Add a new URL</h3>}
|
||||
header="Add a new URL"
|
||||
description="This will add a URL to a list of allowed URLs that can interact with your Authentication services for this project."
|
||||
>
|
||||
<Form
|
||||
validateOnBlur
|
||||
@@ -184,31 +186,24 @@ const RedirectUrls = () => {
|
||||
>
|
||||
{() => {
|
||||
return (
|
||||
<div className="mb-4 space-y-4 pt-4">
|
||||
<div className="px-5">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will add a URL to a list of allowed URLs that can interact with your
|
||||
Authentication services for this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-overlay-border border-t" />
|
||||
<div className="px-5">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input id="url" name="url" label="URL" placeholder="https://mydomain.com" />
|
||||
</div>
|
||||
<div className="border-overlay-border border-t" />
|
||||
<div className="px-5">
|
||||
</Modal.Content>
|
||||
<DialogSectionSeparator />
|
||||
<Modal.Content>
|
||||
<Button
|
||||
block
|
||||
form="new-redirect-url-form"
|
||||
htmlType="submit"
|
||||
size="medium"
|
||||
size="small"
|
||||
disabled={isUpdatingConfig}
|
||||
loading={isUpdatingConfig}
|
||||
>
|
||||
Add URL
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
@@ -220,37 +215,35 @@ const RedirectUrls = () => {
|
||||
header={<h3 className="text-sm">Remove URL</h3>}
|
||||
onCancel={() => setSelectedUrlToDelete(undefined)}
|
||||
>
|
||||
<div className="mb-4 space-y-4 pt-4">
|
||||
<div className="px-5">
|
||||
<p className="mb-2 text-sm text-foreground-light">
|
||||
Are you sure you want to remove{' '}
|
||||
<span className="text-foreground">{selectedUrlToDelete}</span>?
|
||||
</p>
|
||||
<p className="text-foreground-light text-sm">
|
||||
This URL will no longer work with your authentication configuration.
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-overlay-border border-t"></div>
|
||||
<div className="flex gap-3 px-5">
|
||||
<Button
|
||||
block
|
||||
type="default"
|
||||
size="medium"
|
||||
onClick={() => setSelectedUrlToDelete(undefined)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
type="warning"
|
||||
loading={isUpdatingConfig}
|
||||
onClick={() => onConfirmDeleteUrl(selectedUrlToDelete)}
|
||||
>
|
||||
{isUpdatingConfig ? 'Removing...' : 'Remove URL'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<p className="mb-2 text-sm text-foreground-light">
|
||||
Are you sure you want to remove{' '}
|
||||
<span className="text-foreground">{selectedUrlToDelete}</span>?
|
||||
</p>
|
||||
<p className="text-foreground-light text-sm">
|
||||
This URL will no longer work with your authentication configuration.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<Button
|
||||
block
|
||||
type="default"
|
||||
size="medium"
|
||||
onClick={() => setSelectedUrlToDelete(undefined)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
type="warning"
|
||||
loading={isUpdatingConfig}
|
||||
onClick={() => onConfirmDeleteUrl(selectedUrlToDelete)}
|
||||
>
|
||||
{isUpdatingConfig ? 'Removing...' : 'Remove URL'}
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -72,56 +72,52 @@ const CreateUserModal = ({ visible, setVisible }: CreateUserModalProps) => {
|
||||
>
|
||||
{() => (
|
||||
<Loading active={isLoading}>
|
||||
<div className="space-y-6 py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
id="email"
|
||||
autoComplete="off"
|
||||
label="User Email"
|
||||
icon={<IconMail />}
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="user@example.com"
|
||||
disabled={isCreatingUser || isLoading}
|
||||
/>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Input
|
||||
id="email"
|
||||
autoComplete="off"
|
||||
label="User Email"
|
||||
icon={<IconMail />}
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="user@example.com"
|
||||
disabled={isCreatingUser || isLoading}
|
||||
/>
|
||||
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
label="User Password"
|
||||
placeholder="••••••••"
|
||||
icon={<IconLock />}
|
||||
disabled={isCreatingUser || isLoading}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
label="User Password"
|
||||
placeholder="••••••••"
|
||||
icon={<IconLock />}
|
||||
disabled={isCreatingUser || isLoading}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
value="true"
|
||||
id="autoConfirmUser"
|
||||
name="autoConfirmUser"
|
||||
label="Auto Confirm User?"
|
||||
size="medium"
|
||||
description="Creates the user without sending them a confirmation email"
|
||||
defaultChecked={true}
|
||||
disabled={isCreatingUser || isLoading}
|
||||
/>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
|
||||
<Modal.Content>
|
||||
<Button
|
||||
block
|
||||
size="small"
|
||||
htmlType="submit"
|
||||
loading={isCreatingUser}
|
||||
disabled={!canCreateUsers || isCreatingUser || isLoading}
|
||||
>
|
||||
Create user
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Checkbox
|
||||
value="true"
|
||||
id="autoConfirmUser"
|
||||
name="autoConfirmUser"
|
||||
label="Auto Confirm User?"
|
||||
size="medium"
|
||||
description="Creates the user without sending them a confirmation email"
|
||||
defaultChecked={true}
|
||||
disabled={isCreatingUser || isLoading}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<Button
|
||||
block
|
||||
size="small"
|
||||
htmlType="submit"
|
||||
loading={isCreatingUser}
|
||||
disabled={!canCreateUsers || isCreatingUser || isLoading}
|
||||
>
|
||||
Create user
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Loading>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -61,7 +61,7 @@ const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => {
|
||||
onSubmit={onInviteUser}
|
||||
>
|
||||
{() => (
|
||||
<div className="space-y-6 py-4">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="email"
|
||||
@@ -85,7 +85,7 @@ const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => {
|
||||
Invite user
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -132,15 +132,13 @@ const AddNewPaymentMethodModal = ({
|
||||
onCancel={onLocalCancel}
|
||||
className="PAYMENT"
|
||||
>
|
||||
<div className="py-4 space-y-4">
|
||||
<Elements stripe={stripePromise} options={options}>
|
||||
<AddPaymentMethodForm
|
||||
returnUrl={returnUrl}
|
||||
onCancel={onLocalCancel}
|
||||
onConfirm={onLocalConfirm}
|
||||
/>
|
||||
</Elements>
|
||||
</div>
|
||||
<Elements stripe={stripePromise} options={options}>
|
||||
<AddPaymentMethodForm
|
||||
returnUrl={returnUrl}
|
||||
onCancel={onLocalCancel}
|
||||
onConfirm={onLocalConfirm}
|
||||
/>
|
||||
</Elements>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -55,23 +55,33 @@ const AddPaymentMethodForm = ({ returnUrl, onCancel, onConfirm }: AddPaymentMeth
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Modal.Content>
|
||||
<div
|
||||
className={`transition ${isSaving ? 'pointer-events-none opacity-75' : 'opacity-100'}`}
|
||||
>
|
||||
<PaymentElement />
|
||||
</div>
|
||||
<Modal.Content
|
||||
className={`transition ${isSaving ? 'pointer-events-none opacity-75' : 'opacity-100'}`}
|
||||
>
|
||||
<PaymentElement className="[.p-LinkAutofillPrompt]:pt-0" />
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Button block htmlType="submit" type="primary" loading={isSaving} disabled={isSaving}>
|
||||
Save
|
||||
</Button>
|
||||
<Button htmlType="button" type="default" onClick={onCancel} block disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center space-x-2">
|
||||
<Button
|
||||
htmlType="button"
|
||||
size="small"
|
||||
type="default"
|
||||
onClick={onCancel}
|
||||
block
|
||||
disabled={isSaving}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
htmlType="submit"
|
||||
size="small"
|
||||
type="primary"
|
||||
loading={isSaving}
|
||||
disabled={isSaving}
|
||||
>
|
||||
Add payment method
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -10,174 +10,172 @@ interface SpendCapModalProps {
|
||||
const SpendCapModal = ({ visible, onHide }: SpendCapModalProps) => {
|
||||
return (
|
||||
<Modal hideFooter visible={visible} size="xlarge" header="Spend cap" onCancel={() => onHide()}>
|
||||
<div className="py-4 space-y-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
Enabling the spend cap sets a limit on your usage to stay within your plan's quota,
|
||||
which controls costs but can limit service. Disabling the spend cap removes these
|
||||
limits, but any extra usage beyond the plan's limit will be charged per usage.
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
Take a look at the following table to see which resources are chargeable and how they
|
||||
are charged:
|
||||
</p>
|
||||
{/* Maybe instead of a table, show something more interactive like a spend cap playground */}
|
||||
{/* Maybe ideate this in Figma first but this is good enough for now */}
|
||||
<div className="border rounded border-control bg-overlay-hover">
|
||||
<div className="flex items-center px-4 pt-2 pb-1">
|
||||
<p className="w-[50%] text-sm text-foreground-light">Item</p>
|
||||
<p className="w-[25%] text-sm text-foreground-light">Limit</p>
|
||||
<p className="w-[25%] text-sm text-foreground-light">Rate</p>
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
Enabling the spend cap sets a limit on your usage to stay within your plan's quota,
|
||||
which controls costs but can limit service. Disabling the spend cap removes these
|
||||
limits, but any extra usage beyond the plan's limit will be charged per usage.
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
Take a look at the following table to see which resources are chargeable and how they
|
||||
are charged:
|
||||
</p>
|
||||
{/* Maybe instead of a table, show something more interactive like a spend cap playground */}
|
||||
{/* Maybe ideate this in Figma first but this is good enough for now */}
|
||||
<div className="border rounded border-control bg-overlay-hover">
|
||||
<div className="flex items-center px-4 pt-2 pb-1">
|
||||
<p className="w-[50%] text-sm text-foreground-light">Item</p>
|
||||
<p className="w-[25%] text-sm text-foreground-light">Limit</p>
|
||||
<p className="w-[25%] text-sm text-foreground-light">Rate</p>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Database size</p>
|
||||
<p className="w-[25%] text-sm">8GB</p>
|
||||
<p className="w-[25%] text-sm">$0.125 per GB</p>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Database size</p>
|
||||
<p className="w-[25%] text-sm">8GB</p>
|
||||
<p className="w-[25%] text-sm">$0.125 per GB</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Egress</p>
|
||||
<p className="w-[25%] text-sm">250GB</p>
|
||||
<p className="w-[25%] text-sm">$0.09 per GB</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Egress</p>
|
||||
<p className="w-[25%] text-sm">250GB</p>
|
||||
<p className="w-[25%] text-sm">$0.09 per GB</p>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<div className="flex w-[50%] items-center space-x-2">
|
||||
<p className="text-sm">Auth MAUs</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="transition opacity-50 cursor-pointer hover:opacity-100"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<div className="flex w-[50%] items-center space-x-2">
|
||||
<p className="text-sm">Auth MAUs</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="transition opacity-50 cursor-pointer hover:opacity-100"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Monthly Active Users: A user that has made an API request in the
|
||||
last month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Monthly Active Users: A user that has made an API request in the last
|
||||
month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="w-[25%] text-sm">100,000</p>
|
||||
<p className="w-[25%] text-sm">$0.00325 per user</p>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<div className="flex w-[50%] items-center space-x-2">
|
||||
<p className="text-sm">Single Sign-On (SAML 2.0) MAUs</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="transition opacity-50 cursor-pointer hover:opacity-100"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<p className="w-[25%] text-sm">100,000</p>
|
||||
<p className="w-[25%] text-sm">$0.00325 per user</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<div className="flex w-[50%] items-center space-x-2">
|
||||
<p className="text-sm">Single Sign-On (SAML 2.0) MAUs</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="transition opacity-50 cursor-pointer hover:opacity-100"
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Single Sign-On Monthly Active Users: A user that has made an API
|
||||
request in the last month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow', // background
|
||||
'border border-background', //border
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Single Sign-On Monthly Active Users: A user that has made an API
|
||||
request in the last month
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="w-[25%] text-sm">50</p>
|
||||
<p className="w-[25%] text-sm">$0.015 per user</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Storage size</p>
|
||||
<p className="w-[25%] text-sm">100GB</p>
|
||||
<p className="w-[25%] text-sm">$0.021 per GB</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Storage Image Transformations</p>
|
||||
<p className="w-[25%] text-sm">100 origin images</p>
|
||||
<p className="w-[25%] text-sm">$5 per 1000 origin images</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Realtime concurrent peak connections</p>
|
||||
<p className="w-[25%] text-sm">500</p>
|
||||
<p className="w-[25%] text-sm">$10 per 1000</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Realtime messages</p>
|
||||
<p className="w-[25%] text-sm">5 Million</p>
|
||||
<p className="w-[25%] text-sm">$2.50 per Million</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Function invocations</p>
|
||||
<p className="w-[25%] text-sm">2 Million</p>
|
||||
<p className="w-[25%] text-sm">$2 per Million</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Function count</p>
|
||||
<p className="w-[25%] text-sm">100 functions</p>
|
||||
<p className="w-[25%] text-sm">$10 per 100 functions</p>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
<p className="w-[25%] text-sm">50</p>
|
||||
<p className="w-[25%] text-sm">$0.015 per user</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm">
|
||||
See{' '}
|
||||
<Link
|
||||
href="https://supabase.com/pricing"
|
||||
className="text-brand"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span>pricing page</span>
|
||||
</Link>{' '}
|
||||
for a full overview.
|
||||
</p>
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Storage size</p>
|
||||
<p className="w-[25%] text-sm">100GB</p>
|
||||
<p className="w-[25%] text-sm">$0.021 per GB</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Storage Image Transformations</p>
|
||||
<p className="w-[25%] text-sm">100 origin images</p>
|
||||
<p className="w-[25%] text-sm">$5 per 1000 origin images</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Realtime concurrent peak connections</p>
|
||||
<p className="w-[25%] text-sm">500</p>
|
||||
<p className="w-[25%] text-sm">$10 per 1000</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Realtime messages</p>
|
||||
<p className="w-[25%] text-sm">5 Million</p>
|
||||
<p className="w-[25%] text-sm">$2.50 per Million</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Function invocations</p>
|
||||
<p className="w-[25%] text-sm">2 Million</p>
|
||||
<p className="w-[25%] text-sm">$2 per Million</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Function count</p>
|
||||
<p className="w-[25%] text-sm">100 functions</p>
|
||||
<p className="w-[25%] text-sm">$10 per 100 functions</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button block type="primary" onClick={() => onHide()}>
|
||||
Understood
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
|
||||
<p className="text-sm">
|
||||
See{' '}
|
||||
<Link
|
||||
href="https://supabase.com/pricing"
|
||||
className="text-brand"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span>pricing page</span>
|
||||
</Link>{' '}
|
||||
for a full overview.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button block type="primary" onClick={() => onHide()}>
|
||||
Understood
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ const CreateBranchModal = ({ visible, onClose }: CreateBranchModalProps) => {
|
||||
header="Create a new preview branch"
|
||||
confirmText="Create Preview Branch"
|
||||
>
|
||||
<Modal.Content className="pt-3 pb-1">
|
||||
<Modal.Content className="">
|
||||
{isLoadingConnections && <GenericSkeletonLoader />}
|
||||
{isErrorConnections && (
|
||||
<AlertError
|
||||
@@ -175,7 +175,7 @@ const CreateBranchModal = ({ visible, onClose }: CreateBranchModalProps) => {
|
||||
|
||||
<Modal.Separator />
|
||||
|
||||
<Modal.Content className="pt-1 pb-3 space-y-3">
|
||||
<Modal.Content className="space-y-3">
|
||||
<p className="text-sm">
|
||||
Choose a Git Branch to base your Preview Branch on. Any migration changes added to
|
||||
this Git Branch will be run on this new Preview Branch.
|
||||
@@ -220,21 +220,19 @@ const CreateBranchModal = ({ visible, onClose }: CreateBranchModalProps) => {
|
||||
|
||||
<Modal.Separator />
|
||||
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2 py-2 pb-4">
|
||||
<Button disabled={isCreating} type="default" onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
form={formId}
|
||||
disabled={isCreating || !canSubmit}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
Create Preview branch
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button disabled={isCreating} type="default" onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
form={formId}
|
||||
disabled={isCreating || !canSubmit}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
Create Preview branch
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</form>
|
||||
|
||||
@@ -380,44 +380,40 @@ const PITRSelection = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4 py-3">
|
||||
<Modal.Content>
|
||||
<div className="py-2 space-y-1">
|
||||
<p className="text-sm text-foreground-light">Your database will be restored to:</p>
|
||||
<Modal.Content>
|
||||
<div className="py-2 space-y-1">
|
||||
<p className="text-sm text-foreground-light">Your database will be restored to:</p>
|
||||
</div>
|
||||
<div className="py-2 flex flex-col gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-mono text-foreground-lighter">{selectedTimezone?.text}</p>
|
||||
<p className="text-2xl">{recoveryTimeString}</p>
|
||||
</div>
|
||||
<div className="py-2 flex flex-col gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-mono text-foreground-lighter">
|
||||
{selectedTimezone?.text}
|
||||
</p>
|
||||
<p className="text-2xl">{recoveryTimeString}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-mono text-foreground-lighter">(UTC+00:00)</p>
|
||||
<p className="text-2xl">{recoveryTimeStringUtc}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-mono text-foreground-lighter">(UTC+00:00)</p>
|
||||
<p className="text-2xl">{recoveryTimeStringUtc}</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="This action cannot be undone, not cancelled once started"
|
||||
>
|
||||
Any changes made to your database after this point in time will be lost. This includes
|
||||
any changes to your project's storage and authentication.
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Restores may take from a few minutes up to several hours depending on the size of your
|
||||
database. During this period, your project will not be available, until the
|
||||
restoration is completed.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="This action cannot be undone, not canceled once started"
|
||||
>
|
||||
Any changes made to your database after this point in time will be lost. This includes
|
||||
any changes to your project's storage and authentication.
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Restores may take from a few minutes up to several hours depending on the size of your
|
||||
database. During this period, your project will not be available, until the restoration
|
||||
is completed.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -130,7 +130,7 @@ const EnableExtensionModal = ({ visible, extension, onCancel }: EnableExtensionM
|
||||
>
|
||||
{({ values }: any) => {
|
||||
return (
|
||||
<div className="space-y-4 py-4">
|
||||
<>
|
||||
<Modal.Content>
|
||||
{fetchingSchemaInfo || isSchemasLoading ? (
|
||||
<div className="space-y-2">
|
||||
@@ -218,19 +218,16 @@ const EnableExtensionModal = ({ visible, extension, onCancel }: EnableExtensionM
|
||||
</Alert_Shadcn_>
|
||||
</Modal.Content>
|
||||
)}
|
||||
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isEnabling} onClick={() => onCancel()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isEnabling} loading={isEnabling}>
|
||||
Enable extension
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isEnabling} onClick={() => onCancel()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isEnabling} loading={isEnabling}>
|
||||
Enable extension
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
@@ -31,7 +31,7 @@ export const ProtectedSchemaModal = ({
|
||||
}
|
||||
onCancel={() => onClose()}
|
||||
>
|
||||
<Modal.Content className="py-4 space-y-2">
|
||||
<Modal.Content className="space-y-2">
|
||||
<p className="text-sm">
|
||||
The following schemas are managed by Supabase and are currently protected from write
|
||||
access through the dashboard.
|
||||
|
||||
@@ -41,14 +41,12 @@ const DeleteRoleModal = ({ role, visible, onClose }: DeleteRoleModalProps) => {
|
||||
header={<h3>Confirm to delete role "{role?.name}"</h3>}
|
||||
loading={isDeleting}
|
||||
>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
This will automatically revoke any membership of this role in other roles, and this
|
||||
action cannot be undone.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
This will automatically revoke any membership of this role in other roles, and this action
|
||||
cannot be undone.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,14 +44,12 @@ const DeleteWrapperModal = ({ selectedWrapper, onClose }: DeleteWrapperModalProp
|
||||
onConfirm={() => onConfirmDelete()}
|
||||
header={`Confirm to disable ${selectedWrapper?.name}`}
|
||||
>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
Are you sure you want to disable {selectedWrapper?.name}? This will also remove all
|
||||
tables created with this wrapper.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
Are you sure you want to disable {selectedWrapper?.name}? This will also remove all tables
|
||||
created with this wrapper.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { Check, Clipboard } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { forwardRef, useState } from 'react'
|
||||
import { cn } from 'ui'
|
||||
|
||||
const CommandRender = ({ commands }: any) => {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{commands.map((item: any, idx: number) => {
|
||||
return <Command key={`command-${idx}`} item={item} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const CommandRender = forwardRef<HTMLDivElement, { commands: any[]; className?: string }>(
|
||||
({ commands, className }, ref) => {
|
||||
return (
|
||||
<div ref={ref} className={cn('space-y-4', className)}>
|
||||
{commands.map((item: any, idx: number) => (
|
||||
<Command key={`command-${idx}`} item={item} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
CommandRender.displayName = 'CommandRender'
|
||||
|
||||
export default CommandRender
|
||||
|
||||
|
||||
+5
-7
@@ -289,13 +289,11 @@ const EdgeFunctionDetails = () => {
|
||||
onCancel={() => setShowDeleteModal(false)}
|
||||
onConfirm={onConfirmDelete}
|
||||
>
|
||||
<div className="py-6">
|
||||
<Modal.Content>
|
||||
<Alert withIcon variant="warning" title="This action cannot be undone">
|
||||
Ensure that you have made a backup if you want to restore your edge function
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<Alert withIcon variant="warning" title="This action cannot be undone">
|
||||
Ensure that you have made a backup if you want to restore your edge function
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
<Modal.Content className="py-4 space-y-3">
|
||||
<Modal.Content className="space-y-3">
|
||||
<Input id="name" label="Secret name" />
|
||||
<Input
|
||||
id="value"
|
||||
|
||||
@@ -7,8 +7,8 @@ import TerminalInstructions from './TerminalInstructions'
|
||||
const FunctionsEmptyState = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="grid max-w-7xl gap-y-12 py-12 lg:grid-cols-12 lg:gap-x-16">
|
||||
<div className="col-span-5 space-y-4">
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="max-w-lg text-base text-foreground">
|
||||
Scalable functions to run your code with no server management.
|
||||
</p>
|
||||
@@ -38,7 +38,9 @@ const FunctionsEmptyState = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<TerminalInstructions />
|
||||
<div className="col-span-8 bg-surface-100 px-5 py-4 border rounded-md">
|
||||
<TerminalInstructions />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useParams } from 'common/hooks'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { Button } from 'ui'
|
||||
import { ElementRef, forwardRef, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
CollapsibleContent_Shadcn_,
|
||||
CollapsibleTrigger_Shadcn_,
|
||||
Collapsible_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import CommandRender from 'components/interfaces/Functions/CommandRender'
|
||||
import { useAccessTokensQuery } from 'data/access-tokens/access-tokens-query'
|
||||
@@ -10,15 +15,16 @@ import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
|
||||
import { Code, ExternalLink, Maximize2, Minimize2, Terminal } from 'lucide-react'
|
||||
import type { Commands } from './Functions.types'
|
||||
|
||||
interface TerminalInstructionsProps {
|
||||
interface TerminalInstructionsProps
|
||||
extends React.ComponentPropsWithoutRef<typeof Collapsible_Shadcn_> {
|
||||
closable?: boolean
|
||||
removeBorder?: boolean
|
||||
}
|
||||
|
||||
const TerminalInstructions = ({
|
||||
closable = false,
|
||||
removeBorder = false,
|
||||
}: TerminalInstructionsProps) => {
|
||||
const TerminalInstructions = forwardRef<
|
||||
React.ElementRef<typeof Collapsible_Shadcn_>,
|
||||
TerminalInstructionsProps
|
||||
>(({ closable = false, removeBorder = false, ...props }, ref) => {
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const [showInstructions, setShowInstructions] = useState(!closable)
|
||||
@@ -84,72 +90,79 @@ const TerminalInstructions = ({
|
||||
]
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`col-span-7 overflow-hidden transition-all rounded bg-surface-100 ${
|
||||
removeBorder ? '' : 'border shadow'
|
||||
}`}
|
||||
style={{ maxHeight: showInstructions ? 500 : 80 }}
|
||||
<Collapsible_Shadcn_
|
||||
ref={ref}
|
||||
open={showInstructions}
|
||||
className="w-full"
|
||||
onOpenChange={() => setShowInstructions(!showInstructions)}
|
||||
{...props}
|
||||
>
|
||||
<div className="px-8 py-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-x-3">
|
||||
<div className="flex items-center justify-center w-8 h-8 p-2 border rounded bg-alternative">
|
||||
<Terminal strokeWidth={2} />
|
||||
</div>
|
||||
<h4>Create your first Edge Function via the CLI</h4>
|
||||
<CollapsibleTrigger_Shadcn_ className="flex w-full justify-between" disabled={!closable}>
|
||||
<div className="flex items-center gap-x-3">
|
||||
<div className="flex items-center justify-center w-8 h-8 p-2 border rounded bg-alternative">
|
||||
<Terminal strokeWidth={2} />
|
||||
</div>
|
||||
{closable && (
|
||||
<div className="cursor-pointer" onClick={() => setShowInstructions(!showInstructions)}>
|
||||
{showInstructions ? (
|
||||
<Minimize2 size={16} strokeWidth={1.5} />
|
||||
) : (
|
||||
<Maximize2 size={16} strokeWidth={1.5} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<h4>Create your first Edge Function via the CLI</h4>
|
||||
</div>
|
||||
<CommandRender commands={commands} />
|
||||
</div>
|
||||
{tokens && tokens.length === 0 ? (
|
||||
<div className="px-8 py-6 space-y-3 border-t">
|
||||
<div>
|
||||
<h3 className="text-base text-foreground">You may need to create an access token</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
You can create a secure access token in your account section
|
||||
</p>
|
||||
{closable && (
|
||||
<div className="cursor-pointer" onClick={() => setShowInstructions(!showInstructions)}>
|
||||
{showInstructions ? (
|
||||
<Minimize2 size={16} strokeWidth={1.5} />
|
||||
) : (
|
||||
<Maximize2 size={16} strokeWidth={1.5} />
|
||||
)}
|
||||
</div>
|
||||
<Button type="default" onClick={() => router.push('/account/tokens')}>
|
||||
Access tokens
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-8 py-6 space-y-3 border-t">
|
||||
<div>
|
||||
<h3 className="text-base text-foreground">Need help?</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Read the documentation, or browse some sample code.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<a href="https://supabase.com/docs/guides/functions" target="_blank" rel="noreferrer">
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild type="default" icon={<Code />}>
|
||||
<a
|
||||
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Examples
|
||||
</a>
|
||||
)}
|
||||
</CollapsibleTrigger_Shadcn_>
|
||||
<CollapsibleContent_Shadcn_ className="w-full transition-all data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
||||
<CommandRender commands={commands} className="my-4" />
|
||||
{tokens && tokens.length === 0 ? (
|
||||
<div className="px-8 py-4 space-y-3 border-t">
|
||||
<div>
|
||||
<p className="text-sm text-foreground">You may need to create an access token</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
You can create a secure access token in your account section
|
||||
</p>
|
||||
</div>
|
||||
<Button type="default" onClick={() => router.push('/account/tokens')}>
|
||||
Access tokens
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-4 space-y-3 border-t">
|
||||
<div>
|
||||
<h3 className="text-base text-foreground">Need help?</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Read the documentation, or browse some sample code.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/functions"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild type="default" icon={<Code />}>
|
||||
<a
|
||||
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Examples
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CollapsibleContent_Shadcn_>
|
||||
</Collapsible_Shadcn_>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
TerminalInstructions.displayName = 'TerminalInstructions'
|
||||
|
||||
export default TerminalInstructions
|
||||
@@ -170,8 +170,8 @@ const Connect = () => {
|
||||
</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className={cn('sm:max-w-5xl p-0 bg-surface-100')}>
|
||||
<DialogHeader className="pb-0">
|
||||
<DialogContent className={cn('sm:max-w-5xl p-0')}>
|
||||
<DialogHeader className="pb-3">
|
||||
<DialogTitle>Connect to your project</DialogTitle>
|
||||
<DialogDescription>
|
||||
Get the connection strings and environment variables for your app
|
||||
|
||||
+6
-8
@@ -62,14 +62,12 @@ const ChangePaymentMethodModal = ({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
Upon clicking confirm, all future charges will be deducted from the card ending with{' '}
|
||||
{selectedPaymentMethod?.card?.last4}. There are no immediate charges.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
Upon clicking confirm, all future charges will be deducted from the card ending with{' '}
|
||||
{selectedPaymentMethod?.card?.last4}. There are no immediate charges.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+5
-7
@@ -54,13 +54,11 @@ const DeletePaymentMethodModal = ({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<Alert withIcon variant="info" title="This will permanently delete your payment method.">
|
||||
<p>You can re-add the payment method any time.</p>
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<Alert withIcon variant="info" title="This will permanently delete your payment method.">
|
||||
<p>You can re-add the payment method any time.</p>
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+71
-73
@@ -71,82 +71,80 @@ const DowngradeModal = ({
|
||||
header={`Confirm to downgrade to ${selectedPlan?.name} plan`}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6">
|
||||
<div className="space-y-2">
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Downgrading to the Free plan will lead to reductions in your organization's quota"
|
||||
>
|
||||
<p>
|
||||
If you're already past the limits of the Free plan, your projects could become
|
||||
unresponsive or enter read only mode.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
{((previousProjectAddons.length ?? 0) > 0 ||
|
||||
(hasInstancesOnMicro && downgradingToNano)) && (
|
||||
<Alert title={`Projects affected by the downgrade`} variant="warning" withIcon>
|
||||
<ul className="space-y-1 max-h-[100px] overflow-y-auto">
|
||||
{previousProjectAddons.map((project) => (
|
||||
<ProjectDowngradeListItem key={project.ref} projectAddon={project} />
|
||||
))}
|
||||
|
||||
{projects
|
||||
.filter((it) => it.infra_compute_size === 'micro')
|
||||
.map((project) => (
|
||||
<li className="list-disc ml-6" key={project.id}>
|
||||
{project.name}: Compute will be downgraded. Project will also{' '}
|
||||
<span className="font-bold">need to be restarted</span>.
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul className="mt-4 space-y-5 text-sm">
|
||||
<li className="flex gap-3">
|
||||
<div>
|
||||
<IconPauseCircle />
|
||||
</div>
|
||||
<span>Projects will be paused after a week of inactivity</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div className="flex gap-3 mb-2">
|
||||
<div>
|
||||
<IconMinusCircle />
|
||||
</div>
|
||||
<span>Add ons from all projects under this organization will be removed.</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li className="flex gap-3">
|
||||
<IconAlertOctagon w={14} className="flex-shrink-0" />
|
||||
<div>
|
||||
<strong>Before you downgrade to the {selectedPlan?.name} plan, consider:</strong>
|
||||
<ul className="space-y-2 mt-2">
|
||||
<li className="list-disc ml-4">
|
||||
Your projects no longer require their respective add ons.
|
||||
</li>
|
||||
<li className="list-disc ml-4">
|
||||
Your resource consumption are well within the {selectedPlan?.name} plan's quota.
|
||||
</li>
|
||||
<li className="list-disc ml-4">
|
||||
Alternatively, you may also transfer projects across organizations.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{subscription?.billing_via_partner === true && subscription.billing_partner === 'fly' && (
|
||||
<p className="mt-4 text-sm">
|
||||
Your organization will be downgraded at the end of your current billing cycle.
|
||||
<div className="space-y-2">
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Downgrading to the Free plan will lead to reductions in your organization's quota"
|
||||
>
|
||||
<p>
|
||||
If you're already past the limits of the Free plan, your projects could become
|
||||
unresponsive or enter read only mode.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
{((previousProjectAddons.length ?? 0) > 0 ||
|
||||
(hasInstancesOnMicro && downgradingToNano)) && (
|
||||
<Alert title={`Projects affected by the downgrade`} variant="warning" withIcon>
|
||||
<ul className="space-y-1 max-h-[100px] overflow-y-auto">
|
||||
{previousProjectAddons.map((project) => (
|
||||
<ProjectDowngradeListItem key={project.ref} projectAddon={project} />
|
||||
))}
|
||||
|
||||
{projects
|
||||
.filter((it) => it.infra_compute_size === 'micro')
|
||||
.map((project) => (
|
||||
<li className="list-disc ml-6" key={project.id}>
|
||||
{project.name}: Compute will be downgraded. Project will also{' '}
|
||||
<span className="font-bold">need to be restarted</span>.
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ul className="mt-4 space-y-5 text-sm">
|
||||
<li className="flex gap-3">
|
||||
<div>
|
||||
<IconPauseCircle />
|
||||
</div>
|
||||
<span>Projects will be paused after a week of inactivity</span>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<div className="flex gap-3 mb-2">
|
||||
<div>
|
||||
<IconMinusCircle />
|
||||
</div>
|
||||
<span>Add ons from all projects under this organization will be removed.</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li className="flex gap-3">
|
||||
<IconAlertOctagon w={14} className="flex-shrink-0" />
|
||||
<div>
|
||||
<strong>Before you downgrade to the {selectedPlan?.name} plan, consider:</strong>
|
||||
<ul className="space-y-2 mt-2">
|
||||
<li className="list-disc ml-4">
|
||||
Your projects no longer require their respective add ons.
|
||||
</li>
|
||||
<li className="list-disc ml-4">
|
||||
Your resource consumption are well within the {selectedPlan?.name} plan's quota.
|
||||
</li>
|
||||
<li className="list-disc ml-4">
|
||||
Alternatively, you may also transfer projects across organizations.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{subscription?.billing_via_partner === true && subscription.billing_partner === 'fly' && (
|
||||
<p className="mt-4 text-sm">
|
||||
Your organization will be downgraded at the end of your current billing cycle.
|
||||
</p>
|
||||
)}
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ const ExitSurveyModal = ({ visible, subscription, projects, onClose }: ExitSurve
|
||||
header="We're sad that you're leaving"
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
We always strive to improve Supabase as much as we can. Please let us know the reasons
|
||||
you are canceling your subscription so that we can improve in the future.
|
||||
|
||||
+26
-30
@@ -22,36 +22,32 @@ const MembersExceedLimitModal = ({ visible, onClose }: MembersExceedLimitModalPr
|
||||
header="Your organization has members who have exceeded their free project limits"
|
||||
onCancel={onClose}
|
||||
>
|
||||
<div className="py-4 space-y-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The following members have reached their maximum limits for the number of active free
|
||||
plan projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="pl-5 text-sm list-disc text-foreground-light">
|
||||
{(membersExceededLimit || []).map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit} free
|
||||
projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to downgrade this project.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button htmlType="button" type="default" onClick={onClose} block>
|
||||
Understood
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The following members have reached their maximum limits for the number of active free
|
||||
plan projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="pl-5 text-sm list-disc text-foreground-light">
|
||||
{(membersExceededLimit || []).map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit} free
|
||||
projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to downgrade this project.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center gap-2">
|
||||
<Button htmlType="button" type="default" onClick={onClose} block>
|
||||
Understood
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -359,7 +359,7 @@ const PlanUpdateSidePanel = () => {
|
||||
overlayClassName="pointer-events-none"
|
||||
header={`Confirm ${planMeta?.change_type === 'downgrade' ? 'downgrade' : 'upgrade'} to ${subscriptionPlanMeta?.name}`}
|
||||
>
|
||||
<Modal.Content className="mt-4">
|
||||
<Modal.Content>
|
||||
{subscriptionPreviewError && (
|
||||
<AlertError
|
||||
error={subscriptionPreviewError}
|
||||
|
||||
+34
-36
@@ -71,20 +71,19 @@ const UpgradeSurveyModal = ({
|
||||
cancelText="Skip"
|
||||
header="We're excited for your upgrade"
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
What reasons motivated your decision to upgrade? Your feedback helps us improve
|
||||
Supabase as much as we can.
|
||||
</p>
|
||||
<div className="space-y-8 mt-6">
|
||||
<div className="flex flex-wrap gap-2" data-toggle="buttons">
|
||||
{upgradeReasons.map((option) => {
|
||||
const active = selectedReasons.find((x) => x === option)
|
||||
return (
|
||||
<label
|
||||
key={option}
|
||||
className={`
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
What reasons motivated your decision to upgrade? Your feedback helps us improve Supabase
|
||||
as much as we can.
|
||||
</p>
|
||||
<div className="space-y-8 mt-6">
|
||||
<div className="flex flex-wrap gap-2" data-toggle="buttons">
|
||||
{upgradeReasons.map((option) => {
|
||||
const active = selectedReasons.find((x) => x === option)
|
||||
return (
|
||||
<label
|
||||
key={option}
|
||||
className={`
|
||||
flex cursor-pointer items-center space-x-2 rounded-md py-1
|
||||
pl-2 pr-3 text-center text-sm
|
||||
shadow-sm transition-all duration-100
|
||||
@@ -94,28 +93,27 @@ const UpgradeSurveyModal = ({
|
||||
: ` bg-border-strong text-foreground opacity-25 hover:opacity-50`
|
||||
}
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="options"
|
||||
value={option}
|
||||
className="hidden"
|
||||
onClick={dispatchSelectedReasons}
|
||||
/>
|
||||
<div>{option}</div>
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="text-area-text-sm">
|
||||
<Input.TextArea
|
||||
id="message"
|
||||
name="message"
|
||||
value={message}
|
||||
onChange={(event: any) => setMessage(event.target.value)}
|
||||
label="Anything else that we can improve on?"
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="options"
|
||||
value={option}
|
||||
className="hidden"
|
||||
onClick={dispatchSelectedReasons}
|
||||
/>
|
||||
<div>{option}</div>
|
||||
</label>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className="text-area-text-sm">
|
||||
<Input.TextArea
|
||||
id="message"
|
||||
name="message"
|
||||
value={message}
|
||||
onChange={(event: any) => setMessage(event.target.value)}
|
||||
label="Anything else that we can improve on?"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
|
||||
+3
-3
@@ -58,7 +58,7 @@ const DeleteOrganizationButton = () => {
|
||||
onCancel={() => setIsOpen(false)}
|
||||
header={
|
||||
<div className="flex items-baseline gap-2">
|
||||
<h5 className="text-sm text-foreground">Delete organization</h5>
|
||||
<span>Delete organization</span>
|
||||
<span className="text-xs text-foreground-lighter">Are you sure?</span>
|
||||
</div>
|
||||
}
|
||||
@@ -70,7 +70,7 @@ const DeleteOrganizationButton = () => {
|
||||
validate={onValidate}
|
||||
>
|
||||
{() => (
|
||||
<div className="space-y-4 py-3">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-lighter">
|
||||
This action <span className="text-foreground">cannot</span> be undone. This will
|
||||
@@ -106,7 +106,7 @@ const DeleteOrganizationButton = () => {
|
||||
I understand, delete this organization
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -36,26 +36,26 @@ const DeleteAppModal = ({ selectedApp, onClose }: DeleteAppModalProps) => {
|
||||
onConfirm={onConfirmDelete}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-4">
|
||||
<Alert withIcon variant="warning" title="This action cannot be undone">
|
||||
Deleting {selectedApp?.name} will invalidate any access tokens from this application
|
||||
that were authorized by users.
|
||||
</Alert>
|
||||
<ul className="mt-4 space-y-5">
|
||||
<li className="flex gap-3 text-sm">
|
||||
<IconLock w={14} className="flex-shrink-0" />
|
||||
<div>
|
||||
<strong>Before you remove this application, consider:</strong>
|
||||
<ul className="space-y-2 mt-2">
|
||||
<li className="list-disc ml-4">
|
||||
No users are currently using this application. It will no longer be available
|
||||
for use after deletion.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Alert withIcon variant="warning" title="This action cannot be undone">
|
||||
Deleting {selectedApp?.name} will invalidate any access tokens from this application that
|
||||
were authorized by users.
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<ul className="space-y-5">
|
||||
<li className="flex gap-3 text-sm">
|
||||
<IconLock w={14} className="flex-shrink-0" />
|
||||
<div>
|
||||
<strong>Before you remove this application, consider:</strong>
|
||||
<ul className="space-y-2 mt-2">
|
||||
<li className="list-disc ml-4">
|
||||
No users are currently using this application. It will no longer be available for
|
||||
use after deletion.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
+12
-16
@@ -383,33 +383,29 @@ const PublishAppSidePanel = ({
|
||||
onCancel={() => setShowPreview(false)}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="pt-4 pb-2 px-2 flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<p>Authorize API access for {values.name}</p>
|
||||
<Badge variant="brand">Preview</Badge>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="px-2 py-4">
|
||||
<AuthorizeRequesterDetails
|
||||
icon={iconUrl || null}
|
||||
name={values.name}
|
||||
domain={values.website}
|
||||
scopes={scopes}
|
||||
/>
|
||||
<div className="pt-4 space-y-2">
|
||||
<p className="prose text-sm">
|
||||
Select an organization to grant API access to
|
||||
</p>
|
||||
<div className="border border-control text-foreground-light rounded px-4 py-2 text-sm bg-surface-200">
|
||||
Organizations that you have access to will be listed here
|
||||
</div>
|
||||
<AuthorizeRequesterDetails
|
||||
icon={iconUrl || null}
|
||||
name={values.name}
|
||||
domain={values.website}
|
||||
scopes={scopes}
|
||||
/>
|
||||
<div className="pt-4 space-y-2">
|
||||
<p className="prose text-sm">Select an organization to grant API access to</p>
|
||||
<div className="border border-control text-foreground-light rounded px-4 py-2 text-sm bg-surface-200">
|
||||
Organizations that you have access to will be listed here
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="pt-2 pb-3 flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="prose text-xs">
|
||||
This is what your users will see when authorizing with your app
|
||||
</p>
|
||||
|
||||
@@ -36,26 +36,26 @@ const RevokeAppModal = ({ selectedApp, onClose }: RevokeAppModalProps) => {
|
||||
onConfirm={onConfirmDelete}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-4">
|
||||
<Alert withIcon variant="warning" title="This action cannot be undone">
|
||||
{selectedApp?.name} application will no longer have access to your organization's
|
||||
settings and projects.
|
||||
</Alert>
|
||||
<ul className="mt-4 space-y-5">
|
||||
<li className="flex gap-3 text-sm">
|
||||
<IconLock w={14} className="flex-shrink-0" />
|
||||
<div>
|
||||
<strong>Before you remove this app, consider:</strong>
|
||||
<ul className="space-y-2 mt-2">
|
||||
<li className="list-disc ml-4">
|
||||
No users are currently using this application. The application will no longer
|
||||
have access to your organization after being revoked.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<Alert withIcon variant="warning" title="This action cannot be undone">
|
||||
{selectedApp?.name} application will no longer have access to your organization's settings
|
||||
and projects.
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<ul className="space-y-5">
|
||||
<li className="flex gap-3 text-sm">
|
||||
<IconLock w={14} className="flex-shrink-0" />
|
||||
<div>
|
||||
<strong>Before you remove this app, consider:</strong>
|
||||
<ul className="space-y-2 mt-2">
|
||||
<li className="list-disc ml-4">
|
||||
No users are currently using this application. The application will no longer have
|
||||
access to your organization after being revoked.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
+35
-44
@@ -150,53 +150,44 @@ const InviteMemberButton = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="w-full py-4">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
{roles && (
|
||||
<Listbox
|
||||
id="role"
|
||||
name="role"
|
||||
label="Member role"
|
||||
error={
|
||||
invalidRoleSelected
|
||||
? `You need additional permissions to assign users the role of ${selectedRole?.name}`
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{roles.map((role: any) => (
|
||||
<Listbox.Option key={role.id} value={role.id} label={role.name}>
|
||||
{role.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Input
|
||||
autoFocus
|
||||
id="email"
|
||||
icon={<IconMail />}
|
||||
placeholder="Enter email address"
|
||||
label="Email address"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
{roles && (
|
||||
<Listbox
|
||||
id="role"
|
||||
name="role"
|
||||
label="Member role"
|
||||
error={
|
||||
invalidRoleSelected
|
||||
? `You need additional permissions to assign users the role of ${selectedRole?.name}`
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{roles.map((role: any) => (
|
||||
<Listbox.Option key={role.id} value={role.id} label={role.name}>
|
||||
{role.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
)}
|
||||
<Input
|
||||
autoFocus
|
||||
id="email"
|
||||
icon={<IconMail />}
|
||||
placeholder="Enter email address"
|
||||
label="Email address"
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="pt-2 pb-3">
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
htmlType="submit"
|
||||
disabled={isInviting || invalidRoleSelected}
|
||||
loading={isInviting}
|
||||
>
|
||||
Invite new member
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
htmlType="submit"
|
||||
disabled={isInviting || invalidRoleSelected}
|
||||
loading={isInviting}
|
||||
>
|
||||
Invite new member
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -165,43 +165,38 @@ const MembersView = ({ searchString }: MembersViewProps) => {
|
||||
onCancel={() => setUserRoleChangeModalVisible(false)}
|
||||
header="Change role of member"
|
||||
>
|
||||
<div className="flex flex-col gap-2 py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-light">
|
||||
You are changing the role of{' '}
|
||||
<span className="text-foreground">{getUserDisplayName(selectedMember)}</span> from{' '}
|
||||
<span className="text-foreground">{getRoleNameById(selectedMember?.oldRoleId)}</span>{' '}
|
||||
to{' '}
|
||||
<span className="text-foreground">{getRoleNameById(selectedMember?.newRoleId)}</span>
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-foreground">
|
||||
By changing the role of this member their permissions will change.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
type="default"
|
||||
block
|
||||
size="medium"
|
||||
onClick={() => setUserRoleChangeModalVisible(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
type="warning"
|
||||
size="medium"
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
onClick={() => handleRoleChange()}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-light">
|
||||
You are changing the role of{' '}
|
||||
<span className="text-foreground">{getUserDisplayName(selectedMember)}</span> from{' '}
|
||||
<span className="text-foreground">{getRoleNameById(selectedMember?.oldRoleId)}</span> to{' '}
|
||||
<span className="text-foreground">{getRoleNameById(selectedMember?.newRoleId)}</span>
|
||||
</p>
|
||||
<p className="mt-3 text-sm text-foreground">
|
||||
By changing the role of this member their permissions will change.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex gap-3">
|
||||
<Button
|
||||
type="default"
|
||||
block
|
||||
size="medium"
|
||||
onClick={() => setUserRoleChangeModalVisible(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
type="warning"
|
||||
size="medium"
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
onClick={() => handleRoleChange()}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
+50
-54
@@ -48,61 +48,57 @@ const RolesHelperModal = () => {
|
||||
header="Permissions for each role"
|
||||
onCancel={() => setShowModal(!showModal)}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
The following table shows the corresponding permissions for each available role in the
|
||||
dashboard.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<div className="rounded border border-default bg-surface-200">
|
||||
<div className="flex items-center border-b border-control">
|
||||
<div className={permissionColumnClassName}>Permissions</div>
|
||||
<div className={roleColumnClassName}>Owner</div>
|
||||
<div className={roleColumnClassName}>Administrator</div>
|
||||
<div className={roleColumnClassName}>Developer</div>
|
||||
<div className={roleColumnClassName}>Read-only {accessTooltip}</div>
|
||||
<div className={roleColumnClassName}>Billing-only {accessTooltip}</div>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[425px] overflow-y-auto">
|
||||
{PERMISSIONS_MAPPING.map((group) => (
|
||||
<Fragment key={group.title}>
|
||||
<div className="flex items-center border-b border-control py-2 px-4 last:border-none">
|
||||
<div className="w-[100%] text-sm">{group.title}</div>
|
||||
</div>
|
||||
{group.actions.map((action, idx) => (
|
||||
<div
|
||||
key={`${group.title}-${idx}`}
|
||||
className="flex items-center border-b border-control bg-overlay-hover last:border-none"
|
||||
>
|
||||
<div className={permissionColumnClassName}>{action.description}</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.owner && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.admin && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.developer && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.read_only && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.billing_only && (
|
||||
<IconCheck size={14} strokeWidth={2} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
The following table shows the corresponding permissions for each available role in the
|
||||
dashboard.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<div className="rounded border border-default bg-surface-200">
|
||||
<div className="flex items-center border-b border-control">
|
||||
<div className={permissionColumnClassName}>Permissions</div>
|
||||
<div className={roleColumnClassName}>Owner</div>
|
||||
<div className={roleColumnClassName}>Administrator</div>
|
||||
<div className={roleColumnClassName}>Developer</div>
|
||||
<div className={roleColumnClassName}>Read-only {accessTooltip}</div>
|
||||
<div className={roleColumnClassName}>Billing-only {accessTooltip}</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[425px] overflow-y-auto">
|
||||
{PERMISSIONS_MAPPING.map((group) => (
|
||||
<Fragment key={group.title}>
|
||||
<div className="flex items-center border-b border-control py-2 px-4 last:border-none">
|
||||
<div className="w-[100%] text-sm">{group.title}</div>
|
||||
</div>
|
||||
{group.actions.map((action, idx) => (
|
||||
<div
|
||||
key={`${group.title}-${idx}`}
|
||||
className="flex items-center border-b border-control bg-overlay-hover last:border-none"
|
||||
>
|
||||
<div className={permissionColumnClassName}>{action.description}</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.owner && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.admin && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.developer && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.read_only && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
<div className={roleColumnClassName}>
|
||||
{action.permissions.billing_only && <IconCheck size={14} strokeWidth={2} />}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -47,29 +47,25 @@ export const SendMessageModal = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-4 flex flex-col gap-y-4">
|
||||
<Input
|
||||
label="Message name"
|
||||
size="small"
|
||||
className="flex-grow"
|
||||
value={values.message}
|
||||
onChange={(v) => setValues({ ...values, message: v.target.value })}
|
||||
<Modal.Content className="flex flex-col gap-y-4">
|
||||
<Input
|
||||
label="Message name"
|
||||
size="small"
|
||||
className="flex-grow"
|
||||
value={values.message}
|
||||
onChange={(v) => setValues({ ...values, message: v.target.value })}
|
||||
/>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<p className="text-sm text-scale-1100">Message payload</p>
|
||||
<CodeEditor
|
||||
id="message-payload"
|
||||
language="json"
|
||||
className="!mb-0 h-32 overflow-hidden rounded border"
|
||||
onInputChange={(e: string | undefined) => setValues({ ...values, payload: e ?? '{}' })}
|
||||
options={{ wordWrap: 'off', contextmenu: false }}
|
||||
value={values.payload}
|
||||
/>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<p className="text-sm text-scale-1100">Message payload</p>
|
||||
<CodeEditor
|
||||
id="message-payload"
|
||||
language="json"
|
||||
className="!mb-0 h-32 overflow-hidden rounded border"
|
||||
onInputChange={(e: string | undefined) =>
|
||||
setValues({ ...values, payload: e ?? '{}' })
|
||||
}
|
||||
options={{ wordWrap: 'off', contextmenu: false }}
|
||||
value={values.payload}
|
||||
/>
|
||||
{error !== undefined && <p className="text-sm text-red-900">{error}</p>}
|
||||
</div>
|
||||
{error !== undefined && <p className="text-sm text-red-900">{error}</p>}
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
|
||||
@@ -82,11 +82,9 @@ export const CreateReportModal = ({ visible, onCancel, afterSubmit }: CreateRepo
|
||||
onSubmit={(newVals) => createCustomReport(newVals)}
|
||||
>
|
||||
{() => (
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Input label="Name" id="name" name="name" />
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<Input.TextArea
|
||||
label="Description"
|
||||
id="description"
|
||||
@@ -96,17 +94,15 @@ export const CreateReportModal = ({ visible, onCancel, afterSubmit }: CreateRepo
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isCreating}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isCreating} disabled={isCreating}>
|
||||
Create report
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isCreating}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isCreating} disabled={isCreating}>
|
||||
Create report
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -68,7 +68,7 @@ export const UpdateCustomReportModal = ({
|
||||
onSubmit={onConfirmUpdateReport}
|
||||
>
|
||||
{({ isSubmitting }: { isSubmitting: boolean }) => (
|
||||
<div className="space-y-4 py-4">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input label="Name" id="name" name="name" />
|
||||
</Modal.Content>
|
||||
@@ -82,17 +82,15 @@ export const UpdateCustomReportModal = ({
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Save custom report
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Save custom report
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -60,44 +60,46 @@ const DownloadSnippetModal = ({ id, ...props }: DownloadSnippetModalProps) => {
|
||||
header={<p>Download snippet as local migration file via the Supabase CLI.</p>}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-col items-start justify-between gap-4 py-5 relative">
|
||||
<div className="flex flex-col items-start justify-between gap-4 relative">
|
||||
<Tabs type="underlined" listClassNames="pl-5">
|
||||
{SNIPPETS.map((snippet) => {
|
||||
return (
|
||||
<Tabs.Panel key={snippet.id} id={snippet.id} label={snippet.label} className="px-5">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h2 className="text-lg">{snippet.title}</h2>
|
||||
<Markdown
|
||||
className="text-sm text-scale-1000 [&>p>code]:!break-normal"
|
||||
content={snippet.description}
|
||||
<Tabs.Panel key={snippet.id} id={snippet.id} label={snippet.label}>
|
||||
<Modal.Content className="!py-0">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<p className="text-base">{snippet.title}</p>
|
||||
<Markdown
|
||||
className="text-sm text-scale-1000 [&>p>code]:!break-normal"
|
||||
content={snippet.description}
|
||||
/>
|
||||
</div>
|
||||
<TwoOptionToggle
|
||||
width={75}
|
||||
options={['CLI', 'NPM']}
|
||||
activeOption={selectedView}
|
||||
borderOverride="border-muted"
|
||||
onClickOption={() =>
|
||||
selectedView === 'CLI' ? setSelectedView('NPM') : setSelectedView('CLI')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<TwoOptionToggle
|
||||
width={75}
|
||||
options={['CLI', 'NPM']}
|
||||
activeOption={selectedView}
|
||||
borderOverride="border-muted"
|
||||
onClickOption={() =>
|
||||
selectedView === 'CLI' ? setSelectedView('NPM') : setSelectedView('CLI')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<pre>
|
||||
<CodeBlock
|
||||
language="bash"
|
||||
className="language-bash prose dark:prose-dark max-w-none"
|
||||
>
|
||||
{selectedView === 'CLI' ? snippet.cli : snippet.npm}
|
||||
</CodeBlock>
|
||||
</pre>
|
||||
<pre>
|
||||
<CodeBlock
|
||||
language="bash"
|
||||
className="language-bash prose dark:prose-dark max-w-none"
|
||||
>
|
||||
{selectedView === 'CLI' ? snippet.cli : snippet.npm}
|
||||
</CodeBlock>
|
||||
</pre>
|
||||
</Modal.Content>
|
||||
</Tabs.Panel>
|
||||
)
|
||||
})}
|
||||
</Tabs>
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<p className="text-xs text-lighter mx-5">Run this command from your project directory</p>
|
||||
<div className="flex justify-between items-center gap-x-2 mx-5">
|
||||
<Modal.Content className="w-full flex items-center justify-between pt-0">
|
||||
<p className="text-xs text-lighter">Run this command from your project directory</p>
|
||||
<div className="flex justify-between items-center gap-x-2">
|
||||
<Button asChild type="default" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/cli/local-development#database-migrations"
|
||||
@@ -118,7 +120,7 @@ const DownloadSnippetModal = ({ id, ...props }: DownloadSnippetModalProps) => {
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -82,8 +82,8 @@ const RenameQueryModal = ({ snippet, visible, onCancel, onComplete }: RenameQuer
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ isSubmitting }: { isSubmitting: boolean }) => (
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Input
|
||||
label="Name"
|
||||
id="name"
|
||||
@@ -108,8 +108,6 @@ const RenameQueryModal = ({ snippet, visible, onCancel, onComplete }: RenameQuer
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
<Input.TextArea
|
||||
label="Description"
|
||||
id="description"
|
||||
@@ -121,17 +119,15 @@ const RenameQueryModal = ({ snippet, visible, onCancel, onComplete }: RenameQuer
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Rename query
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Rename query
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -265,41 +265,39 @@ const JWTSettings = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-4 space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
Create a custom JWT secret. Make sure it is a strong combination of characters that
|
||||
cannot be guessed easily.
|
||||
</p>
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle />
|
||||
<AlertTitle_Shadcn_>This will invalidate all existing API keys</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Generating a new JWT secret will invalidate <u className="text-foreground">all</u>{' '}
|
||||
of your API keys, including your <code className="text-xs">service_role</code> and{' '}
|
||||
<code className="text-xs">anon</code> keys. Your project will also be restarted
|
||||
during this process, which will terminate any existing connections.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<Input
|
||||
onChange={(e: any) => setCustomToken(e.target.value)}
|
||||
value={customToken}
|
||||
icon={<IconKey />}
|
||||
type={showCustomTokenInput ? 'text' : 'password'}
|
||||
className="w-full text-left"
|
||||
label="Custom JWT secret"
|
||||
descriptionText="Minimally 32 characters long, '@' and '$' are not allowed."
|
||||
actions={
|
||||
<div className="flex items-center justify-center mr-1">
|
||||
<Button
|
||||
type="default"
|
||||
icon={showCustomTokenInput ? <IconEye /> : <IconEyeOff />}
|
||||
onClick={() => setShowCustomTokenInput(!showCustomTokenInput)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
Create a custom JWT secret. Make sure it is a strong combination of characters that
|
||||
cannot be guessed easily.
|
||||
</p>
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<IconAlertTriangle />
|
||||
<AlertTitle_Shadcn_>This will invalidate all existing API keys</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Generating a new JWT secret will invalidate <u className="text-foreground">all</u> of
|
||||
your API keys, including your <code className="text-xs">service_role</code> and{' '}
|
||||
<code className="text-xs">anon</code> keys. Your project will also be restarted during
|
||||
this process, which will terminate any existing connections.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<Input
|
||||
onChange={(e: any) => setCustomToken(e.target.value)}
|
||||
value={customToken}
|
||||
icon={<IconKey />}
|
||||
type={showCustomTokenInput ? 'text' : 'password'}
|
||||
className="w-full text-left"
|
||||
label="Custom JWT secret"
|
||||
descriptionText="Minimally 32 characters long, '@' and '$' are not allowed."
|
||||
actions={
|
||||
<div className="flex items-center justify-center mr-1">
|
||||
<Button
|
||||
type="default"
|
||||
icon={showCustomTokenInput ? <IconEye /> : <IconEyeOff />}
|
||||
onClick={() => setShowCustomTokenInput(!showCustomTokenInput)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -470,15 +470,13 @@ const ComputeInstanceSidePanel = () => {
|
||||
header="Confirm to upgrade compute instance now"
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6">
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Your project will need to be restarted when changing its compute size"
|
||||
>
|
||||
Your project will be unavailable for up to 2 minutes while the changes take place.
|
||||
</Alert>
|
||||
</div>
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Your project will need to be restarted when changing its compute size"
|
||||
>
|
||||
Your project will be unavailable for up to 2 minutes while the changes take place.
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ const ConfirmDisableReadOnlyModeModal = ({
|
||||
disableReadOnlyMode({ projectRef: ref })
|
||||
}}
|
||||
>
|
||||
<Modal.Content className="py-4 space-y-2">
|
||||
<Modal.Content className="space-y-2">
|
||||
<p className="text-sm">
|
||||
This will temporarily allow writes to your database for the{' '}
|
||||
<span className="text-amber-900">next 15 minutes</span>, during which you can reduce your
|
||||
|
||||
+33
-37
@@ -150,45 +150,41 @@ const ResetDbPassword = ({ disabled = false }) => {
|
||||
loading={isUpdatingPassword}
|
||||
onCancel={() => setShowResetDbPass(false)}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="w-full space-y-8 py-8">
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
copy={password.length > 0}
|
||||
onChange={onDbPassChange}
|
||||
error={passwordStrengthWarning}
|
||||
// @ts-ignore
|
||||
descriptionText={
|
||||
<PasswordStrengthBar
|
||||
passwordStrengthScore={passwordStrengthScore}
|
||||
passwordStrengthMessage={passwordStrengthMessage}
|
||||
password={password}
|
||||
generateStrongPassword={generateStrongPassword}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="w-full space-y-8">
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
copy={password.length > 0}
|
||||
onChange={onDbPassChange}
|
||||
error={passwordStrengthWarning}
|
||||
// @ts-ignore
|
||||
descriptionText={
|
||||
<PasswordStrengthBar
|
||||
passwordStrengthScore={passwordStrengthScore}
|
||||
passwordStrengthMessage={passwordStrengthMessage}
|
||||
password={password}
|
||||
generateStrongPassword={generateStrongPassword}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2 pt-1 pb-2">
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isUpdatingPassword}
|
||||
onClick={() => setShowResetDbPass(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={isUpdatingPassword}
|
||||
disabled={isUpdatingPassword}
|
||||
onClick={() => confirmResetDbPass()}
|
||||
>
|
||||
Reset password
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isUpdatingPassword}
|
||||
onClick={() => setShowResetDbPass(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={isUpdatingPassword}
|
||||
disabled={isUpdatingPassword}
|
||||
onClick={() => confirmResetDbPass()}
|
||||
>
|
||||
Reset password
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -196,7 +196,7 @@ Read more about [disk management](https://supabase.com/docs/guides/platform/data
|
||||
)}
|
||||
|
||||
<Modal
|
||||
header={<h5 className="text-sm text-foreground">Increase Disk Storage Size</h5>}
|
||||
header="Increase Disk Storage Size"
|
||||
size="medium"
|
||||
visible={showResetDbPass}
|
||||
loading={isUpdatingDiskSize}
|
||||
@@ -230,47 +230,43 @@ Read more about [disk management](https://supabase.com/docs/guides/platform/data
|
||||
</Alert_Shadcn_>
|
||||
) : (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="w-full space-y-4 py-6">
|
||||
<Alert_Shadcn_ variant={isAbleToResizeDatabase ? 'default' : 'warning'}>
|
||||
<Info size={16} />
|
||||
<AlertTitle_Shadcn_>
|
||||
This operation is only possible every 6 hours
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
{isAbleToResizeDatabase
|
||||
? `Upon updating your disk size, the next disk size update will only be available from ${dayjs().format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}`
|
||||
: `Your database was last resized at ${dayjs(lastDatabaseResizeAt).format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}. You can resize your database again in approximately ${formattedTimeTillNextAvailableResize}`}
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<InputNumber
|
||||
required
|
||||
id="new-disk-size"
|
||||
label="New disk size"
|
||||
labelOptional="GB"
|
||||
disabled={!isAbleToResizeDatabase}
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="w-full space-y-4">
|
||||
<Alert_Shadcn_ variant={isAbleToResizeDatabase ? 'default' : 'warning'}>
|
||||
<Info size={16} />
|
||||
<AlertTitle_Shadcn_>
|
||||
This operation is only possible every 6 hours
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
{isAbleToResizeDatabase
|
||||
? `Upon updating your disk size, the next disk size update will only be available from ${dayjs().format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}`
|
||||
: `Your database was last resized at ${dayjs(lastDatabaseResizeAt).format(
|
||||
'DD MMM YYYY, HH:mm (ZZ)'
|
||||
)}. You can resize your database again in approximately ${formattedTimeTillNextAvailableResize}`}
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
<InputNumber
|
||||
required
|
||||
id="new-disk-size"
|
||||
label="New disk size"
|
||||
labelOptional="GB"
|
||||
disabled={!isAbleToResizeDatabase}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex space-x-2 justify-end pt-1 pb-3">
|
||||
<Button type="default" onClick={() => setShowResetDbPass(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
disabled={!isAbleToResizeDatabase || isUpdatingDiskSize}
|
||||
loading={isUpdatingDiskSize}
|
||||
>
|
||||
Update disk size
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex space-x-2 justify-end">
|
||||
<Button type="default" onClick={() => setShowResetDbPass(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
disabled={!isAbleToResizeDatabase || isUpdatingDiskSize}
|
||||
loading={isUpdatingDiskSize}
|
||||
>
|
||||
Update disk size
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
|
||||
+85
-90
@@ -156,113 +156,108 @@ const AddRestrictionModal = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will add an IP address range to a list of allowed ranges that can access
|
||||
your database.
|
||||
</p>
|
||||
<InformationBox
|
||||
title="Note: Restrictions only apply to direct connections to your database and Supavisor"
|
||||
description="They do not currently apply to APIs offered over HTTPS, such as PostgREST, Storage, or Authentication."
|
||||
urlLabel="Learn more"
|
||||
url="https://supabase.com/docs/guides/platform/network-restrictions#limitations"
|
||||
/>
|
||||
<div className="flex space-x-4">
|
||||
<div className="w-[55%]">
|
||||
<Input
|
||||
label={`${type} address`}
|
||||
id="ipAddress"
|
||||
name="ipAddress"
|
||||
placeholder={type === 'IPv4' ? '0.0.0.0' : '::0'}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<Input
|
||||
label={
|
||||
<div className="flex items-center space-x-2">
|
||||
<p>CIDR Block Size</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle size="tiny" strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background w-[300px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Classless inter-domain routing (CIDR) notation is the notation
|
||||
used to identify networks and hosts in the networks. The block
|
||||
size tells us how many bits we need to take for the network
|
||||
prefix, and is a value between 0 to{' '}
|
||||
{type === 'IPv4'
|
||||
? IPV4_MAX_CIDR_BLOCK_SIZE
|
||||
: IPV6_MAX_CIDR_BLOCK_SIZE}
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
}
|
||||
id="cidrBlockSize"
|
||||
name="cidrBlockSize"
|
||||
type="number"
|
||||
placeholder={
|
||||
type === 'IPv4'
|
||||
? IPV4_MAX_CIDR_BLOCK_SIZE.toString()
|
||||
: IPV6_MAX_CIDR_BLOCK_SIZE.toString()
|
||||
}
|
||||
min={0}
|
||||
max={type === 'IPv4' ? IPV4_MAX_CIDR_BLOCK_SIZE : IPV6_MAX_CIDR_BLOCK_SIZE}
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will add an IP address range to a list of allowed ranges that can access your
|
||||
database.
|
||||
</p>
|
||||
<InformationBox
|
||||
title="Note: Restrictions only apply to direct connections to your database and Supavisor"
|
||||
description="They do not currently apply to APIs offered over HTTPS, such as PostgREST, Storage, or Authentication."
|
||||
urlLabel="Learn more"
|
||||
url="https://supabase.com/docs/guides/platform/network-restrictions#limitations"
|
||||
/>
|
||||
<div className="flex space-x-4">
|
||||
<div className="w-[55%]">
|
||||
<Input
|
||||
label={`${type} address`}
|
||||
id="ipAddress"
|
||||
name="ipAddress"
|
||||
placeholder={type === 'IPv4' ? '0.0.0.0' : '::0'}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<Input
|
||||
label={
|
||||
<div className="flex items-center space-x-2">
|
||||
<p>CIDR Block Size</p>
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconHelpCircle size="tiny" strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'rounded bg-alternative py-1 px-2 leading-none shadow',
|
||||
'border border-background w-[300px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-xs text-foreground">
|
||||
Classless inter-domain routing (CIDR) notation is the notation
|
||||
used to identify networks and hosts in the networks. The block
|
||||
size tells us how many bits we need to take for the network
|
||||
prefix, and is a value between 0 to{' '}
|
||||
{type === 'IPv4'
|
||||
? IPV4_MAX_CIDR_BLOCK_SIZE
|
||||
: IPV6_MAX_CIDR_BLOCK_SIZE}
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
}
|
||||
id="cidrBlockSize"
|
||||
name="cidrBlockSize"
|
||||
type="number"
|
||||
placeholder={
|
||||
type === 'IPv4'
|
||||
? IPV4_MAX_CIDR_BLOCK_SIZE.toString()
|
||||
: IPV6_MAX_CIDR_BLOCK_SIZE.toString()
|
||||
}
|
||||
min={0}
|
||||
max={type === 'IPv4' ? IPV4_MAX_CIDR_BLOCK_SIZE : IPV6_MAX_CIDR_BLOCK_SIZE}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
{isValidCIDR ? (
|
||||
<Modal.Content>
|
||||
<div className="space-y-1 pt-2 pb-4">
|
||||
<p className="text-sm">
|
||||
The address range <code className="text-xs">{normalizedAddress}</code> will be
|
||||
restricted
|
||||
</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Selected address space: <code className="text-xs">{addressRange.start}</code>{' '}
|
||||
to <code className="text-xs">{addressRange.end}</code>{' '}
|
||||
</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Number of addresses: {availableAddresses}
|
||||
</p>
|
||||
</div>
|
||||
<Modal.Content className="space-y-1">
|
||||
<p className="text-sm">
|
||||
The address range <code className="text-xs">{normalizedAddress}</code> will be
|
||||
restricted
|
||||
</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Selected address space: <code className="text-xs">{addressRange.start}</code> to{' '}
|
||||
<code className="text-xs">{addressRange.end}</code>{' '}
|
||||
</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Number of addresses: {availableAddresses}
|
||||
</p>
|
||||
</Modal.Content>
|
||||
) : (
|
||||
<Modal.Content>
|
||||
<div className="pt-2 pb-4">
|
||||
<div className="h-[68px] flex items-center">
|
||||
<p className="text-sm text-foreground-light">
|
||||
A summary of your restriction will be shown here after entering a valid IP
|
||||
address and CIDR block size. IP addresses will also be normalized.
|
||||
</p>
|
||||
</div>
|
||||
<div className="h-[68px] flex items-center">
|
||||
<p className="text-sm text-foreground-light">
|
||||
A summary of your restriction will be shown here after entering a valid IP
|
||||
address and CIDR block size. IP addresses will also be normalized.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
)}
|
||||
<div className="flex items-center justify-end px-6 py-4 border-t space-x-2">
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isApplying} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isApplying} disabled={isApplying}>
|
||||
Save restriction
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
|
||||
+7
-8
@@ -33,21 +33,20 @@ const AllowAllModal = ({ visible, onClose }: AllowAllModalProps) => {
|
||||
onCancel={onClose}
|
||||
header="Allow access from all IP addresses"
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will allow any IP address to access your project's database. Are you sure?
|
||||
</p>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will allow any IP address to access your project's database. Are you sure?
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<div className="flex items-center justify-end px-6 py-4 border-t space-x-2">
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isApplying} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button loading={isApplying} disabled={isApplying} onClick={() => onSubmit()}>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+14
-15
@@ -31,28 +31,27 @@ const DisallowAllModal = ({ visible, onClose }: DisallowAllModalProps) => {
|
||||
onCancel={onClose}
|
||||
header="Restrict access from all IP addresses"
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will prevent any external IP addresses from accessing your project's database. Are
|
||||
you sure?
|
||||
</p>
|
||||
<InformationBox
|
||||
defaultVisibility
|
||||
hideCollapse
|
||||
title="Note: Restrictions only apply to direct connections to your database and Supavisor"
|
||||
description="They do not currently apply to APIs offered over HTTPS, such as PostgREST, Storage, or Authentication."
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
This will prevent any external IP addresses from accessing your project's database. Are
|
||||
you sure?
|
||||
</p>
|
||||
<InformationBox
|
||||
defaultVisibility
|
||||
hideCollapse
|
||||
title="Note: Restrictions only apply to direct connections to your database and Supavisor"
|
||||
description="They do not currently apply to APIs offered over HTTPS, such as PostgREST, Storage, or Authentication."
|
||||
/>
|
||||
</Modal.Content>
|
||||
<div className="flex items-center justify-end px-6 py-4 border-t space-x-2">
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isApplying} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button loading={isApplying} disabled={isApplying} onClick={() => onSubmit()}>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+16
-18
@@ -66,31 +66,29 @@ const RemoveRestrictionModal = ({
|
||||
onCancel={onClose}
|
||||
header="Confirm to remove restriction"
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="py-6 space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The IPv4 address <code className="text-xs">{selectedRestriction}</code> will be removed
|
||||
from your list of network restrictions
|
||||
{isRemovingOnlyRestriction
|
||||
? '.'
|
||||
: ", and no longer have access to your project's database."}
|
||||
</p>
|
||||
{isRemovingOnlyRestriction && (
|
||||
<Alert withIcon variant="warning" title="Database access will no longer be restricted">
|
||||
Removing all network restrictions will default to your database being accessible from
|
||||
all IP addresses.
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The IPv4 address <code className="text-xs">{selectedRestriction}</code> will be removed
|
||||
from your list of network restrictions
|
||||
{isRemovingOnlyRestriction
|
||||
? '.'
|
||||
: ", and no longer have access to your project's database."}
|
||||
</p>
|
||||
{isRemovingOnlyRestriction && (
|
||||
<Alert withIcon variant="warning" title="Database access will no longer be restricted">
|
||||
Removing all network restrictions will default to your database being accessible from
|
||||
all IP addresses.
|
||||
</Alert>
|
||||
)}
|
||||
</Modal.Content>
|
||||
<div className="flex items-center justify-end px-6 py-4 border-t space-x-2">
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isApplying} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button loading={isApplying} disabled={isApplying} onClick={() => onSubmit()}>
|
||||
Remove restriction
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+106
-107
@@ -99,118 +99,117 @@ const ProjectUpgradeAlert = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="py-6">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">All services are going offline.</p>
|
||||
<p className="text-sm">
|
||||
You will not be able to downgrade back to Postgres {currentPgVersion}.
|
||||
</p>
|
||||
<Alert_Shadcn_ title="Your project will be offline while the upgrade is in progress">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">All services are going offline.</p>
|
||||
<p className="text-sm">
|
||||
You will not be able to downgrade back to Postgres {currentPgVersion}.
|
||||
</p>
|
||||
<Alert_Shadcn_ title="Your project will be offline while the upgrade is in progress">
|
||||
<IconAlertCircle className="h-4 w-4" strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Your project will be offline for up to {durationEstimateHours} hour
|
||||
{durationEstimateHours === 1 ? '' : 's'}
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<p>
|
||||
It is advised to upgrade at a time when there will be minimal impact for
|
||||
your application.
|
||||
</p>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
{(data?.potential_breaking_changes ?? []).length > 0 && (
|
||||
<Alert_Shadcn_ variant="destructive" title="Breaking changes">
|
||||
<IconAlertCircle className="h-4 w-4" strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Your project will be offline for up to {durationEstimateHours} hour
|
||||
{durationEstimateHours === 1 ? '' : 's'}
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
<AlertTitle_Shadcn_>Breaking changes</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="flex flex-col gap-3">
|
||||
<p>
|
||||
It is advised to upgrade at a time when there will be minimal impact for
|
||||
your application.
|
||||
Your project will be upgraded across major versions of Postgres. This
|
||||
may involve breaking changes.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<Button size="tiny" type="default" asChild>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects#caveats"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View docs
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
{(data?.potential_breaking_changes ?? []).length > 0 && (
|
||||
<Alert_Shadcn_ variant="destructive" title="Breaking changes">
|
||||
<IconAlertCircle className="h-4 w-4" strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>Breaking changes</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="flex flex-col gap-3">
|
||||
<p>
|
||||
Your project will be upgraded across major versions of Postgres. This
|
||||
may involve breaking changes.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<Button size="tiny" type="default" asChild>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects#caveats"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View docs
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
{legacyAuthCustomRoles.length > 0 && (
|
||||
<Alert_Shadcn_
|
||||
variant="warning"
|
||||
title="Custom Postgres roles using md5 authentication have been detected"
|
||||
>
|
||||
<IconAlertTriangle className="h-4 w-4" strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Custom Postgres roles will not work automatically after upgrade
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="flex flex-col gap-3">
|
||||
<p>You must run a series of commands after upgrading.</p>
|
||||
<p>
|
||||
This is because new Postgres versions use scram-sha-256 authentication
|
||||
by default and do not support md5, as it has been deprecated.
|
||||
</p>
|
||||
<div>
|
||||
<p className="mb-1">Run the following commands after the upgrade:</p>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<code className="text-xs">
|
||||
{legacyAuthCustomRoles.map((role) => (
|
||||
<div key={role} className="pb-1">
|
||||
ALTER ROLE <span className="text-brand">{role}</span> WITH
|
||||
PASSWORD '<span className="text-brand">newpassword</span>';
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="tiny" type="default" asChild>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects#caveats"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View docs
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
<Listbox
|
||||
id="version"
|
||||
name="version"
|
||||
label="Select the version of Postgres to upgrade to"
|
||||
descriptionText={`Postgres will be upgraded from ${currentPgVersion} to ${
|
||||
selectedVersion?.app_version?.split('supabase-postgres-')[1] ??
|
||||
values.version
|
||||
}`}
|
||||
)}
|
||||
{legacyAuthCustomRoles.length > 0 && (
|
||||
<Alert_Shadcn_
|
||||
variant="warning"
|
||||
title="Custom Postgres roles using md5 authentication have been detected"
|
||||
>
|
||||
{data?.target_upgrade_versions.map((version) => (
|
||||
<Listbox.Option
|
||||
key={version.postgres_version}
|
||||
value={version.postgres_version}
|
||||
label={`${version.postgres_version} (${
|
||||
version.app_version.split('supabase-postgres-')[1]
|
||||
})`}
|
||||
>
|
||||
{version.postgres_version} (
|
||||
{version.app_version.split('supabase-postgres-')[1]})
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 justify-end px-4 py-4 border-t">
|
||||
<IconAlertTriangle className="h-4 w-4" strokeWidth={2} />
|
||||
<AlertTitle_Shadcn_>
|
||||
Custom Postgres roles will not work automatically after upgrade
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="flex flex-col gap-3">
|
||||
<p>You must run a series of commands after upgrading.</p>
|
||||
<p>
|
||||
This is because new Postgres versions use scram-sha-256 authentication
|
||||
by default and do not support md5, as it has been deprecated.
|
||||
</p>
|
||||
<div>
|
||||
<p className="mb-1">Run the following commands after the upgrade:</p>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<code className="text-xs">
|
||||
{legacyAuthCustomRoles.map((role) => (
|
||||
<div key={role} className="pb-1">
|
||||
ALTER ROLE <span className="text-brand">{role}</span> WITH
|
||||
PASSWORD '<span className="text-brand">newpassword</span>';
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="tiny" type="default" asChild>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects#caveats"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View docs
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
<Listbox
|
||||
id="version"
|
||||
name="version"
|
||||
label="Select the version of Postgres to upgrade to"
|
||||
descriptionText={`Postgres will be upgraded from ${currentPgVersion} to ${
|
||||
selectedVersion?.app_version?.split('supabase-postgres-')[1] ??
|
||||
values.version
|
||||
}`}
|
||||
>
|
||||
{data?.target_upgrade_versions.map((version) => (
|
||||
<Listbox.Option
|
||||
key={version.postgres_version}
|
||||
value={version.postgres_version}
|
||||
label={`${version.postgres_version} (${
|
||||
version.app_version.split('supabase-postgres-')[1]
|
||||
})`}
|
||||
>
|
||||
{version.postgres_version} (
|
||||
{version.app_version.split('supabase-postgres-')[1]})
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center space-x-2 justify-end">
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => setShowUpgradeModal(false)}
|
||||
@@ -221,7 +220,7 @@ const ProjectUpgradeAlert = () => {
|
||||
<Button htmlType="submit" disabled={isUpgrading} loading={isUpgrading}>
|
||||
Confirm upgrade
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
|
||||
+208
-218
@@ -7,6 +7,7 @@ import {
|
||||
Alert,
|
||||
Button,
|
||||
IconAlertCircle,
|
||||
IconInfo,
|
||||
IconLoader,
|
||||
IconShield,
|
||||
IconTool,
|
||||
@@ -21,6 +22,7 @@ import { useProjectTransferMutation } from 'data/projects/project-transfer-mutat
|
||||
import { useProjectTransferPreviewQuery } from 'data/projects/project-transfer-preview-query'
|
||||
import { useCheckPermissions, useFlag, useSelectedProject } from 'hooks'
|
||||
import { formatCurrency } from 'lib/helpers'
|
||||
import { InfoIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
const TransferProjectButton = () => {
|
||||
const project = useSelectedProject()
|
||||
@@ -115,7 +117,7 @@ const TransferProjectButton = () => {
|
||||
onCancel={() => toggle()}
|
||||
visible={isOpen}
|
||||
loading={isTransferring}
|
||||
size={'xlarge'}
|
||||
size={'large'}
|
||||
header={`Transfer project ${project?.name}`}
|
||||
customFooter={
|
||||
<div className="flex items-center space-x-2 justify-end">
|
||||
@@ -133,233 +135,221 @@ const TransferProjectButton = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4 py-4 text-foreground-light">
|
||||
<Modal.Content>
|
||||
<p className="text-sm">
|
||||
To transfer projects, the owner must be a member of both the source and target
|
||||
organizations. For further information see our{' '}
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/project-transfer"
|
||||
className="text-brand hover:underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
<Modal.Content className="text-foreground-light">
|
||||
<p className="text-sm">
|
||||
To transfer projects, the owner must be a member of both the source and target
|
||||
organizations. For further information see our{' '}
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/project-transfer"
|
||||
className="text-brand hover:underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
|
||||
<p className="font-bold mt-6 text-sm">Transferring considerations:</p>
|
||||
<p className="mt-6 text-sm">Transferring considerations:</p>
|
||||
|
||||
<ul className="mt-4 space-y-5 text-sm px-4">
|
||||
<li className="flex gap-4">
|
||||
<span className="shrink-0 mt-1">
|
||||
<IconLoader />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-bold">No downtime</p>
|
||||
<p>There is no downtime or restrictions involved when transferring a project.</p>
|
||||
</div>
|
||||
</li>
|
||||
<ul className="mt-4 space-y-5 text-sm">
|
||||
<li className="flex gap-4">
|
||||
<span className="shrink-0 mt-1">
|
||||
<IconLoader />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-bold">No downtime</p>
|
||||
<p>There is no downtime or restrictions involved when transferring a project.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li className="flex gap-4">
|
||||
<span className="shrink-0 mt-1">
|
||||
<IconShield />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-bold">Permissions</p>
|
||||
<p>
|
||||
Depending on your role in the target organization, your level of permissions may
|
||||
change after transfer.
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li className="flex gap-4">
|
||||
<span className="shrink-0 mt-1">
|
||||
<IconTool w={14} className="flex-shrink-0" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-bold">Features</p>
|
||||
<p>
|
||||
Moving your project to an organization with a smaller subscription plan may
|
||||
result in the loss of certain features (i.e. image transformations).
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Modal.Content>
|
||||
<Modal.Content>
|
||||
{organizations && (
|
||||
<div className="mt-8 mx-4 border-t pt-4 space-y-2">
|
||||
{organizations.length === 0 ? (
|
||||
<div className="flex items-center gap-2 bg-surface-200 p-3 text-sm">
|
||||
<IconAlertCircle /> You do not have any organizations with an organization-based
|
||||
subscription.
|
||||
</div>
|
||||
) : (
|
||||
<Listbox
|
||||
label="Select Target Organization"
|
||||
layout="vertical"
|
||||
value={selectedOrg}
|
||||
onChange={(slug) => setSelectedOrg(slug)}
|
||||
placeholder="Select Organization"
|
||||
>
|
||||
<Listbox.Option disabled key="no-results" label="Select Organization" value="">
|
||||
Select Organization
|
||||
</Listbox.Option>
|
||||
{organizations.map((x: any) => (
|
||||
<Listbox.Option
|
||||
key={x.id}
|
||||
label={x.name}
|
||||
value={x.slug}
|
||||
addOnBefore={() => <IconUsers />}
|
||||
>
|
||||
{x.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
)}
|
||||
|
||||
<p className="text-foreground-light text-sm">
|
||||
The target organization needs to use{' '}
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/org-based-billing"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
organization-based billing
|
||||
</Link>
|
||||
. To migrate an organization to the new billing, head to your{' '}
|
||||
<Link
|
||||
href="/org/_/billing"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
organizations billing settings
|
||||
</Link>
|
||||
.
|
||||
<li className="flex gap-4">
|
||||
<span className="shrink-0 mt-1">
|
||||
<IconShield />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-bold">Permissions</p>
|
||||
<p>
|
||||
Depending on your role in the target organization, your level of permissions may
|
||||
change after transfer.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Modal.Content>
|
||||
</li>
|
||||
|
||||
<Loading active={selectedOrg !== undefined && transferPreviewIsLoading}>
|
||||
<Modal.Content>
|
||||
<div className="px-4">
|
||||
{transferPreviewData && transferPreviewData.valid && (
|
||||
<div className="text-sm text-foreground-light p-4 bg-surface-200">
|
||||
{transferPreviewData.source_subscription_plan !==
|
||||
transferPreviewData.target_subscription_plan ? (
|
||||
<div>
|
||||
<p>
|
||||
Your project is currently on the{' '}
|
||||
{transferPreviewData.source_subscription_plan} plan, whereas the target
|
||||
organization uses the {transferPreviewData.target_subscription_plan} plan.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
Your project and the target organization are both on the{' '}
|
||||
{transferPreviewData.source_subscription_plan} subscription plan.
|
||||
</div>
|
||||
)}
|
||||
<li className="flex gap-4">
|
||||
<span className="shrink-0 mt-1">
|
||||
<IconTool w={14} className="flex-shrink-0" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="font-bold">Features</p>
|
||||
<p>
|
||||
Moving your project to an organization with a smaller subscription plan may result
|
||||
in the loss of certain features (i.e. image transformations).
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
{organizations && (
|
||||
<div className="space-y-2">
|
||||
{organizations.length === 0 ? (
|
||||
<div className="flex items-center gap-3 bg-surface-200 p-3 text-sm rounded-md border">
|
||||
<InfoIcon /> You do not have any organizations with an organization-based
|
||||
subscription.
|
||||
</div>
|
||||
) : (
|
||||
<Listbox
|
||||
label="Select Target Organization"
|
||||
layout="vertical"
|
||||
value={selectedOrg}
|
||||
onChange={(slug) => setSelectedOrg(slug)}
|
||||
placeholder="Select Organization"
|
||||
>
|
||||
<Listbox.Option disabled key="no-results" label="Select Organization" value="">
|
||||
Select Organization
|
||||
</Listbox.Option>
|
||||
{organizations.map((x: any) => (
|
||||
<Listbox.Option
|
||||
key={x.id}
|
||||
label={x.name}
|
||||
value={x.slug}
|
||||
addOnBefore={() => <IconUsers />}
|
||||
>
|
||||
{x.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
)}
|
||||
|
||||
<div className="my-4">
|
||||
{transferPreviewData.credits_on_source_organization === 0 ? (
|
||||
<span>
|
||||
{' '}
|
||||
Your current organization won't be granted any prorated credits.
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{' '}
|
||||
Your current organization will be granted{' '}
|
||||
<span className="text-brand">
|
||||
${transferPreviewData.credits_on_source_organization}
|
||||
</span>{' '}
|
||||
in credits as proration.
|
||||
</span>
|
||||
)}
|
||||
{transferPreviewData.costs_on_target_organization === 0 ? (
|
||||
<span>
|
||||
{' '}
|
||||
The target organization won't be charged any immediate upfront payment.
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{' '}
|
||||
The target organization will be billed{' '}
|
||||
<span className="text-brand">
|
||||
{formatCurrency(transferPreviewData.costs_on_target_organization)}
|
||||
</span>{' '}
|
||||
immediately to prorate for the remainder of the billing period.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-foreground-light text-sm">
|
||||
The target organization needs to use{' '}
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/platform/org-based-billing"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
organization-based billing
|
||||
</Link>
|
||||
. To migrate an organization to the new billing, head to your{' '}
|
||||
<Link href="/org/_/billing" target="_blank" rel="noreferrer" className="underline">
|
||||
organizations billing settings
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Modal.Content>
|
||||
|
||||
<Loading active={selectedOrg !== undefined && transferPreviewIsLoading}>
|
||||
<Modal.Content>
|
||||
{transferPreviewData && transferPreviewData.valid && (
|
||||
<div className="text-sm text-foreground-light p-4 bg-surface-200">
|
||||
{transferPreviewData.source_subscription_plan !==
|
||||
transferPreviewData.target_subscription_plan ? (
|
||||
<div>
|
||||
<p>
|
||||
Your project is currently on the{' '}
|
||||
{transferPreviewData.source_subscription_plan} plan, whereas the target
|
||||
organization uses the {transferPreviewData.target_subscription_plan} plan.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
Your project and the target organization are both on the{' '}
|
||||
{transferPreviewData.source_subscription_plan} subscription plan.
|
||||
</div>
|
||||
)}
|
||||
{transferPreviewData && transferPreviewData.warnings.length > 0 && (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Warnings for project transfer"
|
||||
className="mt-3"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{transferPreviewData.warnings.map((warning) => (
|
||||
<p key={warning.key}>{warning.message}</p>
|
||||
))}
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
{transferPreviewData && transferPreviewData.errors.length > 0 && (
|
||||
<Alert withIcon variant="danger" title="Project cannot be transferred">
|
||||
<div className="space-y-1">
|
||||
{transferPreviewData.errors.map((error) => (
|
||||
<p key={error.key}>{error.message}</p>
|
||||
))}
|
||||
</div>
|
||||
{transferPreviewData.members_exceeding_free_project_limit.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members have reached their maximum limits for the number of active
|
||||
Free plan projects within organizations where they are an administrator or
|
||||
owner:
|
||||
</p>
|
||||
<ul className="pl-5 text-sm list-disc text-foreground-light">
|
||||
{(transferPreviewData.members_exceeding_free_project_limit || []).map(
|
||||
(member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.name} (Limit: {member.limit} free projects)
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members will need to either delete, pause, or upgrade one or more of
|
||||
their projects before you can downgrade this project.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
{transferPreviewError && !transferError && (
|
||||
<Alert withIcon variant="danger" title="Project cannot be transferred">
|
||||
<p>{transferPreviewError.message}</p>
|
||||
</Alert>
|
||||
)}
|
||||
{transferError && (
|
||||
<Alert withIcon variant="danger" title="Project cannot be transferred">
|
||||
<p>{transferError.message}</p>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="my-4">
|
||||
{transferPreviewData.credits_on_source_organization === 0 ? (
|
||||
<span> Your current organization won't be granted any prorated credits.</span>
|
||||
) : (
|
||||
<span>
|
||||
{' '}
|
||||
Your current organization will be granted{' '}
|
||||
<span className="text-brand">
|
||||
${transferPreviewData.credits_on_source_organization}
|
||||
</span>{' '}
|
||||
in credits as proration.
|
||||
</span>
|
||||
)}
|
||||
{transferPreviewData.costs_on_target_organization === 0 ? (
|
||||
<span>
|
||||
{' '}
|
||||
The target organization won't be charged any immediate upfront payment.
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{' '}
|
||||
The target organization will be billed{' '}
|
||||
<span className="text-brand">
|
||||
{formatCurrency(transferPreviewData.costs_on_target_organization)}
|
||||
</span>{' '}
|
||||
immediately to prorate for the remainder of the billing period.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
{transferPreviewData && transferPreviewData.warnings.length > 0 && (
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Warnings for project transfer"
|
||||
className="mt-3"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{transferPreviewData.warnings.map((warning) => (
|
||||
<p key={warning.key}>{warning.message}</p>
|
||||
))}
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
{transferPreviewData && transferPreviewData.errors.length > 0 && (
|
||||
<Alert withIcon variant="danger" title="Project cannot be transferred">
|
||||
<div className="space-y-1">
|
||||
{transferPreviewData.errors.map((error) => (
|
||||
<p key={error.key}>{error.message}</p>
|
||||
))}
|
||||
</div>
|
||||
{transferPreviewData.members_exceeding_free_project_limit.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members have reached their maximum limits for the number of active Free
|
||||
plan projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="pl-5 text-sm list-disc text-foreground-light">
|
||||
{(transferPreviewData.members_exceeding_free_project_limit || []).map(
|
||||
(member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.name} (Limit: {member.limit} free projects)
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members will need to either delete, pause, or upgrade one or more of
|
||||
their projects before you can downgrade this project.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
{transferPreviewError && !transferError && (
|
||||
<Alert withIcon variant="danger" title="Project cannot be transferred">
|
||||
<p>{transferPreviewError.message}</p>
|
||||
</Alert>
|
||||
)}
|
||||
{transferError && (
|
||||
<Alert withIcon variant="danger" title="Project cannot be transferred">
|
||||
<p>{transferError.message}</p>
|
||||
</Alert>
|
||||
)}
|
||||
</Modal.Content>
|
||||
</Loading>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ export const UpdateSavedQueryModal = ({
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{({ isSubmitting }: { isSubmitting: boolean }) => (
|
||||
<div className="space-y-4 py-4">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input label="Name" id="name" name="name" />
|
||||
</Modal.Content>
|
||||
@@ -55,17 +55,15 @@ export const UpdateSavedQueryModal = ({
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Save query
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end gap-2">
|
||||
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
|
||||
Save query
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -22,53 +22,47 @@ const UpgradePrompt: React.FC<Props> = ({ show, setShowUpgradePrompt }) => {
|
||||
header="Log retention"
|
||||
onCancel={() => setShowUpgradePrompt(false)}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
Logs can be retained up to a duration of 3 months depending on the plan that your
|
||||
project is on.
|
||||
</p>
|
||||
<div className="border-control bg-surface-300 rounded border">
|
||||
<div className="flex items-center px-4 pt-2 pb-1">
|
||||
<p className="text-foreground-light w-[40%] text-sm">Plan</p>
|
||||
<p className="text-foreground-light w-[60%] text-sm">Retention duration</p>
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
Logs can be retained up to a duration of 3 months depending on the plan that your
|
||||
project is on.
|
||||
</p>
|
||||
<div className="border-control bg-surface-300 rounded border">
|
||||
<div className="flex items-center px-4 pt-2 pb-1">
|
||||
<p className="text-foreground-light w-[40%] text-sm">Plan</p>
|
||||
<p className="text-foreground-light w-[60%] text-sm">Retention duration</p>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Free</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.FREE.text}</p>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Free</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.FREE.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Pro</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.PRO.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Team</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.TEAM.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Enterprise</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.ENTERPRISE.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Pro</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.PRO.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Team</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.TEAM.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Enterprise</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.ENTERPRISE.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button type="default" onClick={() => setShowUpgradePrompt(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button asChild size="tiny">
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>
|
||||
Upgrade
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex justify-end gap-3">
|
||||
<Button type="default" onClick={() => setShowUpgradePrompt(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button asChild size="tiny">
|
||||
<Link href={`/org/${organization?.slug}/billing?panel=subscriptionPlan`}>Upgrade</Link>
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
+38
-48
@@ -292,32 +292,28 @@ const EncryptionKeysManagement = () => {
|
||||
onCancel={() => setSelectedKeyToRemove(undefined)}
|
||||
onConfirm={confirmDeleteKey}
|
||||
loading={isDeleting}
|
||||
header={<h5 className="text-sm text-foreground">Confirm to delete key</h5>}
|
||||
header="Confirm to delete key"
|
||||
>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Deleting a key that's in use will cause any secret or column which depends on it to be unusable."
|
||||
>
|
||||
Do ensure that the key is not currently in use to prevent any issues.
|
||||
</Alert>
|
||||
<p className="text-sm">
|
||||
The following key will be permanently removed and cannot be recovered.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground">
|
||||
{selectedKeyToRemove?.name ?? DEFAULT_KEY_NAME}
|
||||
</p>
|
||||
<p className="text-xs text-foreground-light">
|
||||
<code className="!mx-0">ID: {selectedKeyToRemove?.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title="Deleting a key that's in use will cause any secret or column which depends on it to be unusable."
|
||||
>
|
||||
Do ensure that the key is not currently in use to prevent any issues.
|
||||
</Alert>
|
||||
<p className="text-sm">
|
||||
The following key will be permanently removed and cannot be recovered.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground">
|
||||
{selectedKeyToRemove?.name ?? DEFAULT_KEY_NAME}
|
||||
</p>
|
||||
<p className="text-xs text-foreground-light">
|
||||
<code className="!mx-0">ID: {selectedKeyToRemove?.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
@@ -326,7 +322,7 @@ const EncryptionKeysManagement = () => {
|
||||
size="medium"
|
||||
visible={showAddKeyModal}
|
||||
onCancel={() => setShowAddKeyModal(false)}
|
||||
header={<h5 className="text-sm text-foreground">Add a new key</h5>}
|
||||
header="Add a new key"
|
||||
>
|
||||
<Form
|
||||
id="add-new-key-form"
|
||||
@@ -341,31 +337,25 @@ const EncryptionKeysManagement = () => {
|
||||
>
|
||||
{() => {
|
||||
return (
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm mb-4">
|
||||
Provide a name for your key for easier identification.
|
||||
</p>
|
||||
<div className="space-y-4 pb-4">
|
||||
<Input id="name" label="Key Name" />
|
||||
</div>
|
||||
<>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm">Provide a name for your key for easier identification.</p>
|
||||
<Input id="name" label="Key Name" />
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isCreating}
|
||||
onClick={() => setShowAddKeyModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isCreating} loading={isCreating}>
|
||||
Add key
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isCreating}
|
||||
onClick={() => setShowAddKeyModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isCreating} loading={isCreating}>
|
||||
Add key
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
@@ -84,7 +84,7 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
size="medium"
|
||||
visible={visible}
|
||||
onCancel={onClose}
|
||||
header={<h5 className="text-sm text-foreground">Add new secret</h5>}
|
||||
header="Add new secret"
|
||||
>
|
||||
<Form
|
||||
id="add-new-secret-form"
|
||||
@@ -95,70 +95,64 @@ const AddNewSecretModal = ({ visible, onClose }: AddNewSecretModalProps) => {
|
||||
>
|
||||
{({ isSubmitting }: any) => {
|
||||
return (
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4 pb-4">
|
||||
<Input id="name" label="Name" />
|
||||
<Input id="description" label="Description" labelOptional="Optional" />
|
||||
<Input
|
||||
id="secret"
|
||||
type={showSecretValue ? 'text' : 'password'}
|
||||
label="Secret value"
|
||||
actions={
|
||||
<div className="mr-1">
|
||||
<Button
|
||||
type="default"
|
||||
icon={showSecretValue ? <IconEyeOff /> : <IconEye />}
|
||||
onClick={() => setShowSecretValue(!showSecretValue)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Input id="name" label="Name" />
|
||||
<Input id="description" label="Description" labelOptional="Optional" />
|
||||
<Input
|
||||
id="secret"
|
||||
type={showSecretValue ? 'text' : 'password'}
|
||||
label="Secret value"
|
||||
actions={
|
||||
<div className="mr-1">
|
||||
<Button
|
||||
type="default"
|
||||
icon={showSecretValue ? <IconEyeOff /> : <IconEye />}
|
||||
onClick={() => setShowSecretValue(!showSecretValue)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="py-4 space-y-4">
|
||||
<EncryptionKeySelector
|
||||
id="keyId"
|
||||
nameId="keyName"
|
||||
label="Select a key to encrypt your secret with"
|
||||
labelOptional="Optional"
|
||||
selectedKeyId={selectedKeyId}
|
||||
onSelectKey={setSelectedKeyId}
|
||||
/>
|
||||
<InformationBox
|
||||
icon={<IconHelpCircle size={18} strokeWidth={2} />}
|
||||
url="https://github.com/supabase/vault"
|
||||
urlLabel="Vault documentation"
|
||||
title="What is a key?"
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<p>
|
||||
Keys are used to encrypt data inside your database, and every secret in
|
||||
the Vault is encrypted with a key.
|
||||
</p>
|
||||
<p>
|
||||
You may create different keys for different purposes, such as one for
|
||||
encrypting user data, and another for application data.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<EncryptionKeySelector
|
||||
id="keyId"
|
||||
nameId="keyName"
|
||||
label="Select a key to encrypt your secret with"
|
||||
labelOptional="Optional"
|
||||
selectedKeyId={selectedKeyId}
|
||||
onSelectKey={setSelectedKeyId}
|
||||
/>
|
||||
<InformationBox
|
||||
icon={<IconHelpCircle size={18} strokeWidth={2} />}
|
||||
url="https://github.com/supabase/vault"
|
||||
urlLabel="Vault documentation"
|
||||
title="What is a key?"
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<p>
|
||||
Keys are used to encrypt data inside your database, and every secret in the
|
||||
Vault is encrypted with a key.
|
||||
</p>
|
||||
<p>
|
||||
You may create different keys for different purposes, such as one for
|
||||
encrypting user data, and another for application data.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isSubmitting} onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
|
||||
Add secret
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isSubmitting} onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
|
||||
Add secret
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
@@ -42,24 +42,19 @@ const DeleteSecretModal = ({ selectedSecret, onClose }: DeleteSecretModalProps)
|
||||
onCancel={onClose}
|
||||
onConfirm={onConfirmDeleteSecret}
|
||||
loading={isDeleting}
|
||||
header={<h5 className="text-sm text-foreground">Confirm to delete secret</h5>}
|
||||
header="Confirm to delete secret"
|
||||
>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
The following secret will be permanently removed and cannot be recovered. Are you
|
||||
sure?
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm">{selectedSecret?.description}</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
ID: <span className="font-mono">{selectedSecret?.key_id}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<p className="text-sm">
|
||||
The following secret will be permanently removed and cannot be recovered. Are you sure?
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm">{selectedSecret?.description}</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
ID: <span className="font-mono">{selectedSecret?.key_id}</span>
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -129,56 +129,50 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
)
|
||||
|
||||
return isLoadingSecretValue ? (
|
||||
<div className="p-4">
|
||||
<Modal.Content>
|
||||
<GenericSkeletonLoader />
|
||||
</div>
|
||||
</Modal.Content>
|
||||
) : (
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4 pb-4">
|
||||
<Input id="name" label="Name" />
|
||||
<Input id="description" label="Description" labelOptional="Optional" />
|
||||
<Input
|
||||
id="secret"
|
||||
type={showSecretValue ? 'text' : 'password'}
|
||||
label="Secret value"
|
||||
actions={
|
||||
<div className="mr-1">
|
||||
<Button
|
||||
type="default"
|
||||
icon={showSecretValue ? <IconEyeOff /> : <IconEye />}
|
||||
onClick={() => setShowSecretValue(!showSecretValue)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<Modal.Content className="space-y-4">
|
||||
<Input id="name" label="Name" />
|
||||
<Input id="description" label="Description" labelOptional="Optional" />
|
||||
<Input
|
||||
id="secret"
|
||||
type={showSecretValue ? 'text' : 'password'}
|
||||
label="Secret value"
|
||||
actions={
|
||||
<div className="mr-1">
|
||||
<Button
|
||||
type="default"
|
||||
icon={showSecretValue ? <IconEyeOff /> : <IconEye />}
|
||||
onClick={() => setShowSecretValue(!showSecretValue)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="py-4 space-y-4">
|
||||
<EncryptionKeySelector
|
||||
id="keyId"
|
||||
nameId="keyName"
|
||||
label="Select a key to encrypt your secret with"
|
||||
labelOptional="Optional"
|
||||
selectedKeyId={selectedKeyId}
|
||||
onSelectKey={setSelectedKeyId}
|
||||
/>
|
||||
</div>
|
||||
<Modal.Content className="space-y-4">
|
||||
<EncryptionKeySelector
|
||||
id="keyId"
|
||||
nameId="keyName"
|
||||
label="Select a key to encrypt your secret with"
|
||||
labelOptional="Optional"
|
||||
selectedKeyId={selectedKeyId}
|
||||
onSelectKey={setSelectedKeyId}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isSubmitting} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
|
||||
Update secret
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" disabled={isSubmitting} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
|
||||
Update secret
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
@@ -116,7 +116,7 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => {
|
||||
>
|
||||
{({ values }: { values: any }) => {
|
||||
return (
|
||||
<div className="space-y-4 py-4">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
id="name"
|
||||
@@ -164,8 +164,8 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => {
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content className="py-4">
|
||||
<div className="w-full space-y-4 px-5">
|
||||
<div className="space-y-2">
|
||||
<div className="w-full space-y-5 px-5">
|
||||
<div className="space-y-5">
|
||||
<Toggle
|
||||
id="has_file_size_limit"
|
||||
name="has_file_size_limit"
|
||||
@@ -236,28 +236,21 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => {
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
<div className="w-full border-t border-default !mt-0" />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center space-x-2 justify-end">
|
||||
<Button
|
||||
type="default"
|
||||
htmlType="button"
|
||||
disabled={isCreating}
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={isCreating}
|
||||
disabled={isCreating}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center space-x-2 justify-end">
|
||||
<Button
|
||||
type="default"
|
||||
htmlType="button"
|
||||
disabled={isCreating}
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" loading={isCreating} disabled={isCreating}>
|
||||
Save
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
@@ -112,7 +112,7 @@ const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalProps) =>
|
||||
}, [visible])
|
||||
|
||||
return (
|
||||
<div className="space-y-4 py-4">
|
||||
<>
|
||||
<Modal.Content>
|
||||
<Input
|
||||
disabled
|
||||
@@ -242,23 +242,16 @@ const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalProps) =>
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
<div className="w-full border-t border-default !mt-0" />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center space-x-2 justify-end">
|
||||
<Button type="default" disabled={isUpdating} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={isUpdating}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center space-x-2 justify-end">
|
||||
<Button type="default" disabled={isUpdating} onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" loading={isUpdating} disabled={isUpdating}>
|
||||
Save
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const BranchingPlanNotice = () => {
|
||||
return (
|
||||
<Alert_Shadcn_ className="rounded-none px-7 py-6 [&>svg]:top-6 [&>svg]:left-6 border-0 border-y">
|
||||
<AlertCircleIcon />
|
||||
<AlertTitle_Shadcn_ className="text-base">
|
||||
<AlertTitle_Shadcn_>
|
||||
Database branching is only available on the Pro plan and above
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
|
||||
+29
-30
@@ -138,7 +138,8 @@ const EnableBranchingModal = () => {
|
||||
visible={snap.showEnableBranchingModal}
|
||||
onCancel={() => snap.setShowEnableBranchingModal(false)}
|
||||
className="!bg !max-w-[40rem]"
|
||||
size="xlarge"
|
||||
size="medium"
|
||||
hideClose
|
||||
>
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form
|
||||
@@ -146,7 +147,7 @@ const EnableBranchingModal = () => {
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
onChange={() => setIsValid(false)}
|
||||
>
|
||||
<Modal.Content className="px-7 py-5 flex items-center justify-between space-x-4">
|
||||
<Modal.Content className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<IconGitBranch strokeWidth={2} size={20} />
|
||||
<div>
|
||||
@@ -207,14 +208,14 @@ const EnableBranchingModal = () => {
|
||||
{!hasPitrEnabled && <BranchingPITRNotice />}
|
||||
</>
|
||||
)}
|
||||
<Modal.Content className="px-7 py-6 flex flex-col gap-3">
|
||||
<Modal.Content className="py-6 flex flex-col gap-3">
|
||||
<p className="text-sm text-foreground-light">
|
||||
Please keep in mind the following:
|
||||
</p>
|
||||
<div className="flex flex-row gap-4">
|
||||
<div>
|
||||
<figure className="w-10 h-10 rounded-md bg-warning-200 border border-warning-300 flex items-center justify-center">
|
||||
<IconDollarSign className="text-amber-900" size={20} strokeWidth={2} />
|
||||
<figure className="w-10 h-10 rounded-md bg-warning-200 border border-warning-400 flex items-center justify-center">
|
||||
<IconDollarSign className="text-warning" size={20} strokeWidth={2} />
|
||||
</figure>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
@@ -230,8 +231,8 @@ const EnableBranchingModal = () => {
|
||||
</div>
|
||||
<div className="flex flex-row gap-4 mt-2">
|
||||
<div>
|
||||
<figure className="w-10 h-10 rounded-md bg-warning-200 border border-warning-300 flex items-center justify-center">
|
||||
<IconFileText className="text-amber-900" size={20} strokeWidth={2} />
|
||||
<figure className="w-10 h-10 rounded-md bg-warning-200 border border-warning-400 flex items-center justify-center">
|
||||
<IconFileText className="text-warning" size={20} strokeWidth={2} />
|
||||
</figure>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
@@ -251,29 +252,27 @@ const EnableBranchingModal = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<Modal.Content className="px-7">
|
||||
<div className="flex items-center space-x-2 py-2 pb-4">
|
||||
<Button
|
||||
size="medium"
|
||||
block
|
||||
disabled={isCreating}
|
||||
type="default"
|
||||
onClick={() => snap.setShowEnableBranchingModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
form={formId}
|
||||
disabled={!isSuccess || isCreating || !canSubmit}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
I understand, enable branching
|
||||
</Button>
|
||||
</div>
|
||||
<Modal.Content className="flex items-center gap-3">
|
||||
<Button
|
||||
size="medium"
|
||||
block
|
||||
disabled={isCreating}
|
||||
type="default"
|
||||
onClick={() => snap.setShowEnableBranchingModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
size="medium"
|
||||
form={formId}
|
||||
disabled={!isSuccess || isCreating || !canSubmit}
|
||||
loading={isCreating}
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
>
|
||||
I understand, enable branching
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
|
||||
@@ -175,41 +175,35 @@ const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
|
||||
header="Your organization has members who have exceeded their free project limits"
|
||||
onCancel={() => setShowFreeProjectLimitWarning(false)}
|
||||
>
|
||||
<div className="py-4 space-y-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The following members have reached their maximum limits for the number of active
|
||||
free plan projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="pl-5 text-sm list-disc text-foreground-light">
|
||||
{(membersExceededLimit || []).map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit}{' '}
|
||||
free projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to unpause this project.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
htmlType="button"
|
||||
type="default"
|
||||
onClick={() => setShowFreeProjectLimitWarning(false)}
|
||||
block
|
||||
>
|
||||
Understood
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content className="space-y-2">
|
||||
<p className="text-sm text-foreground-light">
|
||||
The following members have reached their maximum limits for the number of active free
|
||||
plan projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="pl-5 text-sm list-disc text-foreground-light">
|
||||
{(membersExceededLimit || []).map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit} free
|
||||
projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-foreground-light">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to unpause this project.
|
||||
</p>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<Button
|
||||
htmlType="button"
|
||||
type="default"
|
||||
onClick={() => setShowFreeProjectLimitWarning(false)}
|
||||
block
|
||||
>
|
||||
Understood
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -58,11 +58,9 @@ const ConfirmDeleteModal = ({
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<div className="my-4">
|
||||
<Alert withIcon variant="danger" title={`This action cannot be undone.`}>
|
||||
{description}
|
||||
</Alert>
|
||||
</div>
|
||||
<Alert withIcon variant="danger" title={`This action cannot be undone.`}>
|
||||
{description}
|
||||
</Alert>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
+39
-43
@@ -58,51 +58,47 @@ const CustomExpiryModal = ({ onCopyUrl }: CustomExpiryModalProps) => {
|
||||
>
|
||||
{({ values, isSubmitting }: { values: any; isSubmitting: boolean }) => (
|
||||
<>
|
||||
<div className="pt-4 pb-2">
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-light mb-2">
|
||||
Enter the duration for which the URL will be valid for:
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input disabled={isSubmitting} type="number" id="expiresIn" className="w-full" />
|
||||
<Listbox id="units" className="w-[150px]">
|
||||
<Listbox.Option id="days" label="days" value="days">
|
||||
days
|
||||
</Listbox.Option>
|
||||
<Listbox.Option id="weeks" label="weeks" value="weeks">
|
||||
weeks
|
||||
</Listbox.Option>
|
||||
<Listbox.Option id="months" label="months" value="months">
|
||||
months
|
||||
</Listbox.Option>
|
||||
<Listbox.Option id="years" label="years" value="years">
|
||||
years
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</div>
|
||||
{values.expiresIn !== '' && (
|
||||
<p className="text-sm text-foreground-light mt-2">
|
||||
URL will expire on{' '}
|
||||
{dayjs().add(values.expiresIn, values.units).format(DATETIME_FORMAT)}
|
||||
</p>
|
||||
)}
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end space-x-2 pt-1 pb-3">
|
||||
<Button type="default" onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={values.expiresIn === '' || isSubmitting}
|
||||
loading={isSubmitting}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
>
|
||||
Get signed URL
|
||||
</Button>
|
||||
<p className="text-sm text-foreground-light mb-2">
|
||||
Enter the duration for which the URL will be valid for:
|
||||
</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input disabled={isSubmitting} type="number" id="expiresIn" className="w-full" />
|
||||
<Listbox id="units" className="w-[150px]">
|
||||
<Listbox.Option id="days" label="days" value="days">
|
||||
days
|
||||
</Listbox.Option>
|
||||
<Listbox.Option id="weeks" label="weeks" value="weeks">
|
||||
weeks
|
||||
</Listbox.Option>
|
||||
<Listbox.Option id="months" label="months" value="months">
|
||||
months
|
||||
</Listbox.Option>
|
||||
<Listbox.Option id="years" label="years" value="years">
|
||||
years
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</div>
|
||||
{values.expiresIn !== '' && (
|
||||
<p className="text-sm text-foreground-light mt-2">
|
||||
URL will expire on{' '}
|
||||
{dayjs().add(values.expiresIn, values.units).format(DATETIME_FORMAT)}
|
||||
</p>
|
||||
)}
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end space-x-2">
|
||||
<Button type="default" onClick={() => onClose()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={values.expiresIn === '' || isSubmitting}
|
||||
loading={isSubmitting}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
>
|
||||
Get signed URL
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -65,7 +65,7 @@ const MoveItemsModal = ({
|
||||
}
|
||||
>
|
||||
<Modal.Content>
|
||||
<form className="my-4">
|
||||
<form>
|
||||
<div className="relative flex items-center">
|
||||
<Input
|
||||
autoFocus
|
||||
|
||||
+3
-3
@@ -108,8 +108,8 @@ const StoragePoliciesEditor = ({
|
||||
const selectedRoles = policyFormFields.roles
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-8 space-y-8 py-6">
|
||||
<>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<PolicyName
|
||||
name={policyFormFields.name}
|
||||
@@ -137,7 +137,7 @@ const StoragePoliciesEditor = ({
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<PolicyEditorFooter onViewTemplates={onViewTemplates} onReviewPolicy={onReviewPolicy} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+25
-26
@@ -29,34 +29,33 @@ const StoragePoliciesReview = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="space-y-6 py-8">
|
||||
<div className="flex items-center justify-between space-y-8 space-x-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-foreground-light">
|
||||
These are the SQL statements that will be used to create your policies. The suffix
|
||||
appended to the end of your policy name (<code>[hashString]_[number]</code>) just
|
||||
functions as a unique identifier for each of your policies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 overflow-y-auto" style={{ maxHeight: '25rem' }}>
|
||||
{policyStatements.length === 0 && <ReviewEmptyState />}
|
||||
{policyStatements.map((policy, idx) => {
|
||||
let formattedSQLStatement = policy.statement || ''
|
||||
return (
|
||||
<div key={`policy_${idx}`} className="space-y-2">
|
||||
<span>{policy.description}</span>
|
||||
<div className="h-40">
|
||||
<SqlEditor readOnly defaultValue={formattedSQLStatement} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<Modal.Content className="space-y-6">
|
||||
<div className="flex items-center justify-between space-y-8 space-x-4">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm text-foreground-light">
|
||||
These are the SQL statements that will be used to create your policies. The suffix
|
||||
appended to the end of your policy name (<code>[hashString]_[number]</code>) just
|
||||
functions as a unique identifier for each of your policies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 overflow-y-auto" style={{ maxHeight: '25rem' }}>
|
||||
{policyStatements.length === 0 && <ReviewEmptyState />}
|
||||
{policyStatements.map((policy, idx) => {
|
||||
let formattedSQLStatement = policy.statement || ''
|
||||
return (
|
||||
<div key={`policy_${idx}`} className="space-y-2">
|
||||
<span>{policy.description}</span>
|
||||
<div className="h-40">
|
||||
<SqlEditor readOnly defaultValue={formattedSQLStatement} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<div className="flex w-full items-center justify-end gap-2 border-t px-6 py-4 border-default">
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex w-full items-center justify-end gap-2">
|
||||
<Button type="default" onClick={onSelectBack}>
|
||||
Back to edit
|
||||
</Button>
|
||||
@@ -65,7 +64,7 @@ const StoragePoliciesReview = ({
|
||||
Save policy
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+1
-2
@@ -159,9 +159,8 @@ export const CreateCredentialModal = ({ visible, onOpenChange }: CreateCredentia
|
||||
)}
|
||||
/>
|
||||
</DialogSection>
|
||||
|
||||
<DialogFooter>
|
||||
<Button className="mt-4" htmlType="submit" loading={isCreating}>
|
||||
<Button htmlType="submit" loading={isCreating}>
|
||||
Create access key
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -192,7 +192,7 @@ const StorageSettings = () => {
|
||||
{isFreeTier && (
|
||||
<div className="px-6 pb-6">
|
||||
<UpgradeToPro
|
||||
icon={<Clock size={16} />}
|
||||
icon={<Clock size={14} className="text-foreground-muted" />}
|
||||
primaryText="Free Plan has a fixed upload file size limit of 50 MB."
|
||||
secondaryText="Upgrade to the Pro plan for a configurable upload file size limit of up to 50 GB."
|
||||
/>
|
||||
|
||||
@@ -38,8 +38,8 @@ const AISettingsModal = () => {
|
||||
visible={snap.showAiSettingsModal}
|
||||
onCancel={() => snap.setShowAiSettingsModal(false)}
|
||||
>
|
||||
<div className="flex flex-col items-start justify-between gap-y-4 px-6 py-3">
|
||||
<div className="flex justify-between gap-x-8 mr-8 my-4">
|
||||
<Modal.Content className="flex flex-col items-start justify-between gap-y-4">
|
||||
<div className="flex justify-between gap-x-5 mr-8 my-4">
|
||||
<Toggle
|
||||
disabled={IS_PLATFORM && !isOptedInToAI}
|
||||
checked={includeSchemaMetadata}
|
||||
@@ -78,7 +78,7 @@ const AISettingsModal = () => {
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { useParams } from 'common'
|
||||
import { useState } from 'react'
|
||||
import { Button, Modal } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTrigger,
|
||||
Modal,
|
||||
} from 'ui'
|
||||
|
||||
import {
|
||||
EdgeFunctionsListItem,
|
||||
@@ -43,9 +53,18 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
<span className="text-sm text-foreground-lighter">{`${functions.length} function${
|
||||
functions.length > 1 ? 's' : ''
|
||||
} deployed`}</span>
|
||||
<Button type="primary" onClick={() => setShowTerminalInstructions(true)}>
|
||||
Deploy a new function
|
||||
</Button>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="primary" onClick={() => setShowTerminalInstructions(true)}>
|
||||
Deploy a new function
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent size={'large'}>
|
||||
<DialogSection padding="small">
|
||||
<TerminalInstructions />
|
||||
</DialogSection>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<Table
|
||||
head={
|
||||
@@ -73,23 +92,6 @@ const PageLayout: NextPageWithLayout = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Modal
|
||||
size="xlarge"
|
||||
visible={showTerminalInstructions}
|
||||
onCancel={() => setShowTerminalInstructions(false)}
|
||||
header={<h3>Deploying an edge function to your project</h3>}
|
||||
customFooter={
|
||||
<div className="w-full flex items-center justify-end">
|
||||
<Button type="primary" size="tiny" onClick={() => setShowTerminalInstructions(false)}>
|
||||
Confirm
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="py-4">
|
||||
<TerminalInstructions removeBorder />
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -272,43 +272,32 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
<div className="py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-6">
|
||||
<Input layout="horizontal" label="Name" id="name" />
|
||||
<div className="text-area-text-sm">
|
||||
<Input.TextArea
|
||||
layout="horizontal"
|
||||
labelOptional="Optional"
|
||||
label="Description"
|
||||
id="description"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<div className="py-3 border-t bg-surface-100">
|
||||
<Modal.Content>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button
|
||||
size="tiny"
|
||||
type="default"
|
||||
onClick={() => setSaveModalOpen(!saveModalOpen)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
htmlType="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
<Modal.Content className="space-y-6">
|
||||
<Input layout="horizontal" label="Name" id="name" />
|
||||
<div className="text-area-text-sm">
|
||||
<Input.TextArea
|
||||
layout="horizontal"
|
||||
labelOptional="Optional"
|
||||
label="Description"
|
||||
id="description"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end gap-2">
|
||||
<Button size="tiny" type="default" onClick={() => setSaveModalOpen(!saveModalOpen)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
htmlType="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
.StripeElement {
|
||||
width: 100%;
|
||||
padding: 11px 15px 11px 0;
|
||||
}
|
||||
|
||||
.ErrorMessage {
|
||||
|
||||
@@ -166,12 +166,12 @@ const uiConfig = {
|
||||
'100%': { transform: 'scale(0.95)', opacity: 0 },
|
||||
},
|
||||
overlayContentShow: {
|
||||
'0%': { opacity: 0, transform: 'translate(0%, -2%) scale(.96)' },
|
||||
'0%': { opacity: 0, transform: 'translate(0%, -2%) scale(1)' },
|
||||
'100%': { opacity: 1, transform: 'translate(0%, 0%) scale(1)' },
|
||||
},
|
||||
overlayContentHide: {
|
||||
'0%': { opacity: 1, transform: 'translate(0%, 0%) scale(1)' },
|
||||
'100%': { opacity: 0, transform: 'translate(0%, -2%) scale(.96)' },
|
||||
'100%': { opacity: 0, transform: 'translate(0%, -2%) scale(1)' },
|
||||
},
|
||||
dropdownFadeIn: {
|
||||
'0%': { transform: 'scale(0.95)', opacity: 0 },
|
||||
|
||||
@@ -59,11 +59,7 @@ const ConfirmModal = ({
|
||||
>
|
||||
{() => {
|
||||
return (
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-foreground-light">{description}</p>
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<>
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
@@ -90,7 +86,7 @@ const ConfirmModal = ({
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
|
||||
@@ -101,4 +101,4 @@ const WarningIcon: React.FC<SVGProps<SVGSVGElement> & StatusIconProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export { WarningIcon, CriticalIcon }
|
||||
export { WarningIcon, CriticalIcon, InfoIcon }
|
||||
@@ -29,6 +29,7 @@
|
||||
--destructive-400: 6.7deg 60% 20.6%;
|
||||
--destructive-300: 7.5deg 51.3% 15.3%;
|
||||
--destructive-200: 10.9deg 23.4% 9.2%;
|
||||
--background-dialog-default: 0deg 0% 11%;
|
||||
--background-surface-75: 0deg 0% 12.5%;
|
||||
--background-200: 0deg 0% 11%;
|
||||
--foreground-contrast: 0deg 0% 11%;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
--border-secondary: 0deg 0% 14.1%;
|
||||
--border-muted: 0deg 0% 14.1%;
|
||||
--border-default: 0deg 0% 16.1%;
|
||||
--background-dialog-default: 0deg 0% 7.1%;
|
||||
--background-muted: 0deg 0% 14.1%;
|
||||
--background-overlay-hover: 0deg 0% 18%;
|
||||
--background-overlay-default: 0deg 0% 14.1%;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
--border-stronger: 0deg 0% 56.1%;
|
||||
--border-strong: 0deg 0% 83.1%;
|
||||
--border-default: 0deg 0% 87.5%;
|
||||
--background-dialog-default: 0deg 0% 100%;
|
||||
--background-muted: 0deg 0% 96.9%;
|
||||
--background-surface-400: 0deg 0% 89.8%;
|
||||
--background-surface-300: 0deg 0% 92.9%;
|
||||
|
||||
@@ -479,6 +479,10 @@ module.exports = {
|
||||
cssVariable: "var(--background-button-default)",
|
||||
value: "hsl(0, 0%, 18%)"
|
||||
},
|
||||
"background-dialog-DEFAULT": {
|
||||
cssVariable: "var(--background-dialog-default)",
|
||||
value: "hsl(0, 0%, 7.1%)"
|
||||
},
|
||||
"border-DEFAULT": {
|
||||
cssVariable: "var(--border-default)",
|
||||
value: "hsl(0, 0%, 16.1%)"
|
||||
|
||||
@@ -1,189 +1,176 @@
|
||||
'use client'
|
||||
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import React, { forwardRef, useEffect } from 'react'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { cn } from '../../lib/utils/cn'
|
||||
import { AnimationTailwindClasses } from '../../types'
|
||||
import { Button } from '../Button/Button'
|
||||
import IconX from '../Icon/icons/IconX/IconX'
|
||||
import Space from '../Space/Space'
|
||||
import { Button, ButtonVariantProps } from '../Button/Button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogSection,
|
||||
DialogSectionSeparator,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DialogDescription,
|
||||
DialogClose,
|
||||
} from '../shadcn/ui/dialog'
|
||||
|
||||
// import { Transition } from '@tailwindui/react'
|
||||
// Merge Radix Props to surface in the modal component
|
||||
export type ModalProps = RadixProps & Props
|
||||
|
||||
interface RadixProps
|
||||
extends Dialog.DialogProps,
|
||||
Pick<
|
||||
Dialog.DialogContentProps,
|
||||
| 'onOpenAutoFocus'
|
||||
| 'onCloseAutoFocus'
|
||||
| 'onEscapeKeyDown'
|
||||
| 'onPointerDownOutside'
|
||||
| 'onInteractOutside'
|
||||
> {}
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode
|
||||
export interface ModalProps extends React.ComponentProps<typeof DialogContent> {
|
||||
Separator?: React.ComponentType
|
||||
Content?: React.ComponentType
|
||||
visible?: boolean
|
||||
// @deprecated please add the footer directly in component children. This is to prepare for using <DialogFooter/> component
|
||||
customFooter?: React.ReactNode
|
||||
description?: string
|
||||
// @deprecated please add the footer directly in component children. This is to prepare for using <DialogFooter/> component
|
||||
hideFooter?: boolean
|
||||
// @deprecated please add the footer directly in component children. This is to prepare for using <DialogFooter/> component
|
||||
alignFooter?: 'right' | 'left'
|
||||
// @deprecated please add the footer directly in component children. This is to prepare for using <DialogFooter/> component
|
||||
layout?: 'horizontal' | 'vertical'
|
||||
icon?: React.ReactNode
|
||||
loading?: boolean
|
||||
onCancel?: any
|
||||
cancelText?: string
|
||||
onConfirm?: any
|
||||
confirmText?: string
|
||||
/**
|
||||
* @deprecated This prop is no longer being used in the Modal component
|
||||
*/
|
||||
closable?: boolean
|
||||
showIcon?: boolean
|
||||
showCloseButton?: boolean
|
||||
footerBackground?: boolean
|
||||
title?: string | React.ReactNode
|
||||
variant?: 'danger' | 'warning' | 'success'
|
||||
visible: boolean
|
||||
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'
|
||||
style?: React.CSSProperties
|
||||
// @deprecated please add the footer directly in component children. This is to prepare for using <DialogFooter/> component
|
||||
variant?: ButtonVariantProps['type']
|
||||
overlayStyle?: React.CSSProperties
|
||||
contentStyle?: React.CSSProperties
|
||||
className?: string
|
||||
overlayClassName?: string
|
||||
transition?: AnimationTailwindClasses
|
||||
transitionOverlay?: AnimationTailwindClasses
|
||||
// @deprecated please consider using <Dialog/> and <DialogTrigger/> components
|
||||
triggerElement?: React.ReactNode
|
||||
// @deprecated please consider using <Dialog/> and <DialogHeader/> components
|
||||
header?: React.ReactNode
|
||||
modal?: React.ComponentProps<typeof Dialog>['modal']
|
||||
defaultOpen?: React.ComponentProps<typeof Dialog>['defaultOpen']
|
||||
/**
|
||||
* @deprecated No longer in use
|
||||
*/
|
||||
closable?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use ./Dialog instead
|
||||
*/
|
||||
const Modal = ({
|
||||
children,
|
||||
customFooter = undefined,
|
||||
description,
|
||||
hideFooter = false,
|
||||
alignFooter = 'left',
|
||||
layout = 'horizontal',
|
||||
loading = false,
|
||||
cancelText = 'Cancel',
|
||||
onConfirm = () => {},
|
||||
onCancel = () => {},
|
||||
confirmText = 'Confirm',
|
||||
closable = false,
|
||||
showIcon = false,
|
||||
showCloseButton = false,
|
||||
title,
|
||||
footerBackground,
|
||||
icon,
|
||||
variant = 'success',
|
||||
visible = false,
|
||||
size = 'large',
|
||||
style,
|
||||
overlayStyle,
|
||||
contentStyle,
|
||||
className = '',
|
||||
overlayClassName,
|
||||
triggerElement,
|
||||
header,
|
||||
...props
|
||||
}: ModalProps) => {
|
||||
const [open, setOpen] = React.useState(visible ? visible : false)
|
||||
const __styles = styleHandler('modal')
|
||||
interface ModalType
|
||||
extends React.ForwardRefExoticComponent<
|
||||
React.ComponentPropsWithoutRef<typeof DialogContent> & ModalProps
|
||||
> {
|
||||
Content: React.ComponentType<{ children: React.ReactNode; className?: string }>
|
||||
Separator: React.ComponentType
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(visible)
|
||||
}, [visible])
|
||||
// @deprecated use <Dialog/> instead
|
||||
const Modal = forwardRef<
|
||||
React.ElementRef<typeof DialogContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogContent> & ModalProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
customFooter = undefined,
|
||||
description,
|
||||
hideFooter = false,
|
||||
alignFooter = 'left',
|
||||
layout = 'horizontal',
|
||||
loading = false,
|
||||
cancelText = 'Cancel',
|
||||
onConfirm = () => {},
|
||||
onCancel = () => {},
|
||||
confirmText = 'Confirm',
|
||||
showCloseButton = true,
|
||||
footerBackground,
|
||||
variant = 'success',
|
||||
visible = false,
|
||||
size = 'large',
|
||||
style,
|
||||
overlayStyle,
|
||||
contentStyle,
|
||||
className = '',
|
||||
overlayClassName,
|
||||
triggerElement,
|
||||
header,
|
||||
modal,
|
||||
defaultOpen,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [open, setOpen] = React.useState(visible ? visible : false)
|
||||
const __styles = styleHandler('modal')
|
||||
|
||||
const footerContent = customFooter ? (
|
||||
customFooter
|
||||
) : (
|
||||
<Space
|
||||
className="flex w-full space-x-2"
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent:
|
||||
layout === 'vertical' ? 'center' : alignFooter === 'right' ? 'flex-end' : 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Button type="default" onClick={onCancel} disabled={loading}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
type={variant === 'danger' ? 'danger' : 'primary'}
|
||||
useEffect(() => {
|
||||
setOpen(visible)
|
||||
}, [visible])
|
||||
|
||||
const footerContent = customFooter ? (
|
||||
customFooter
|
||||
) : (
|
||||
<div
|
||||
className="flex w-full space-x-2"
|
||||
style={{
|
||||
width: '100%',
|
||||
justifyContent:
|
||||
layout === 'vertical' ? 'center' : alignFooter === 'right' ? 'flex-end' : 'flex-start',
|
||||
}}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
<Button type="default" onClick={onCancel} disabled={loading}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
type={variant === 'danger' ? 'danger' : 'primary'}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
function handleOpenChange(open: boolean) {
|
||||
if (visible !== undefined && !open) {
|
||||
// controlled component behaviour
|
||||
onCancel()
|
||||
} else {
|
||||
// un-controlled component behaviour
|
||||
setOpen(open)
|
||||
function handleOpenChange(open: boolean) {
|
||||
if (visible !== undefined && !open) {
|
||||
// controlled component behavior
|
||||
onCancel()
|
||||
} else {
|
||||
// un-controlled component behavior
|
||||
setOpen(open)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} defaultOpen={defaultOpen} onOpenChange={handleOpenChange} modal={modal}>
|
||||
{triggerElement && <DialogTrigger>{triggerElement}</DialogTrigger>}
|
||||
<DialogContent ref={ref} hideClose={!showCloseButton} {...props} size={size}>
|
||||
{header || description ? (
|
||||
<DialogHeader className={cn('border-b')} padding={'small'}>
|
||||
{header && <DialogTitle>{header}</DialogTitle>}
|
||||
{description && <DialogDescription>{description}</DialogDescription>}
|
||||
</DialogHeader>
|
||||
) : null}
|
||||
{children}
|
||||
{!hideFooter && <DialogFooter padding={'small'}>{footerContent}</DialogFooter>}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
) as ModalType
|
||||
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={handleOpenChange}>
|
||||
{triggerElement && <Dialog.Trigger>{triggerElement}</Dialog.Trigger>}
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className={__styles.overlay} />
|
||||
<Dialog.Overlay className={__styles.scroll_overlay}>
|
||||
<Dialog.Content
|
||||
className={[__styles.base, __styles.size[size], className].join(' ')}
|
||||
onInteractOutside={props.onInteractOutside}
|
||||
onEscapeKeyDown={props.onEscapeKeyDown}
|
||||
>
|
||||
{header && (
|
||||
<div className={__styles.header}>
|
||||
{header}
|
||||
{showCloseButton && (
|
||||
<Button
|
||||
onClick={onCancel}
|
||||
type="text"
|
||||
icon={<IconX size="small" strokeWidth={1.5} />}
|
||||
className="p-0.5 !mt-0"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
{!hideFooter && <div className={__styles.footer}>{footerContent}</div>}
|
||||
</Dialog.Content>
|
||||
</Dialog.Overlay>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
)
|
||||
}
|
||||
const Content = forwardRef<
|
||||
React.ElementRef<typeof DialogSection>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogSection>
|
||||
>(({ ...props }, ref) => {
|
||||
return <DialogSection ref={ref} {...props} padding="small" className={cn(props.className)} />
|
||||
})
|
||||
|
||||
/**
|
||||
* @deprecated Use ./Dialog instead
|
||||
*/
|
||||
function Content({ children, className }: { children: React.ReactNode; className?: string }) {
|
||||
const __styles = styleHandler('modal')
|
||||
return <div className={cn(__styles.content, className)}>{children}</div>
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use ./Dialog instead
|
||||
*/
|
||||
export function Separator() {
|
||||
const __styles = styleHandler('modal')
|
||||
|
||||
return <div className={__styles.separator}></div>
|
||||
}
|
||||
const Separator = forwardRef<
|
||||
React.ElementRef<typeof DialogSectionSeparator>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogSectionSeparator>
|
||||
>(({ ...props }, ref) => {
|
||||
return <DialogSectionSeparator ref={ref} {...props} />
|
||||
})
|
||||
|
||||
Modal.Content = Content
|
||||
Modal.Separator = Separator
|
||||
|
||||
@@ -16,12 +16,12 @@ export const DIALOG_PADDING_X = 'px-7'
|
||||
const DialogPaddingVariants = cva('', {
|
||||
variants: {
|
||||
padding: {
|
||||
default: `${DIALOG_PADDING_Y} ${DIALOG_PADDING_X}`,
|
||||
medium: `${DIALOG_PADDING_Y} ${DIALOG_PADDING_X}`,
|
||||
small: `${DIALOG_PADDING_Y_SMALL} ${DIALOG_PADDING_X_SMALL}`,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
padding: 'default',
|
||||
padding: 'small',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -41,9 +41,7 @@ const DialogOverlay = React.forwardRef<
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
// 'z-50 fixed h-full w-full left-0 top-0',
|
||||
// 'bg-alternative/90 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'bg-alternative/90 backdrop-blur-sm',
|
||||
'bg-black/40 backdrop-blur-sm',
|
||||
'z-50 fixed inset-0 grid place-items-center overflow-y-auto data-open:animate-overlay-show data-closed:animate-overlay-hide',
|
||||
className
|
||||
)}
|
||||
@@ -61,7 +59,7 @@ const DialogContentVariants = cva(
|
||||
'data-[state=closed]:slide-out-to-left-[0%] data-[state=closed]:slide-out-to-top-[0%',
|
||||
'data-[state=open]:slide-in-from-left-[0%] data-[state=open]:slide-in-from-top-[0%]',
|
||||
'sm:rounded-lg md:w-full',
|
||||
'bg-studio'
|
||||
'bg-200'
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
@@ -95,7 +93,7 @@ const DialogContent = React.forwardRef<
|
||||
>
|
||||
{children}
|
||||
{!hideClose && (
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted">
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-20 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
@@ -109,7 +107,7 @@ DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
const DialogHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof DialogPaddingVariants>
|
||||
>(({ className, padding = 'default', ...props }, ref) => (
|
||||
>(({ className, padding, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
{...props}
|
||||
@@ -173,7 +171,7 @@ const DialogClose = React.forwardRef<
|
||||
<DialogPrimitive.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
'opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -187,11 +185,7 @@ const DialogSection = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof DialogPaddingVariants>
|
||||
>(({ className, children, padding, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={cn('px-5 py-3', DialogPaddingVariants({ padding }), className)}
|
||||
>
|
||||
<div ref={ref} {...props} className={cn(DialogPaddingVariants({ padding }), className)}>
|
||||
{children}
|
||||
</div>
|
||||
))
|
||||
@@ -201,7 +195,7 @@ const DialogSectionSeparator = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<div ref={ref} {...props} className={cn('w-full h-px bg-border-overlay', className)} />
|
||||
<div ref={ref} {...props} className={cn('w-full h-px bg-border', className)} />
|
||||
))
|
||||
DialogSectionSeparator.displayName = 'DialogSectionSeparator'
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ export default {
|
||||
"bg-overlay",
|
||||
"bg-overlay-hover",
|
||||
"bg-muted",
|
||||
"bg-button"
|
||||
"bg-button",
|
||||
"bg-dialog"
|
||||
],
|
||||
"border": [
|
||||
"border",
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Follow this setup guide to integrate the Deno language server with your editor:
|
||||
// https://deno.land/manual/getting_started/setup_your_environment
|
||||
// This enables autocomplete, go to definition, etc.
|
||||
|
||||
console.log("Hello from Functions!")
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const { name } = await req.json()
|
||||
const data = {
|
||||
message: `Hello ${name}!`,
|
||||
}
|
||||
|
||||
return new Response(
|
||||
JSON.stringify(data),
|
||||
{ headers: { "Content-Type": "application/json" } },
|
||||
)
|
||||
})
|
||||
|
||||
/* To invoke locally:
|
||||
|
||||
1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start)
|
||||
2. Make an HTTP request:
|
||||
|
||||
curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/hello-world' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{"name":"Functions"}'
|
||||
|
||||
*/
|
||||
Reference in new issue
Block a user