[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:
Jonathan Summers-Muir authored and GitHub committed 2024-06-05 10:06:55 +00:00
1 parent 21890675e5
commit 972773ca87
95 files changed
+2375 -2194

No files matched your search

+44
View File
@@ -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",
+5
View File
@@ -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>
</>
)
}
+20
View File
@@ -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="&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;"
icon={<IconLock />}
disabled={isCreatingUser || isLoading}
autoComplete="new-password"
/>
<Input
id="password"
name="password"
type="password"
label="User Password"
placeholder="&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;"
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
@@ -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>
</>
)
@@ -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
@@ -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>
)
}
@@ -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,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>
)
@@ -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.
@@ -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>
)
}
@@ -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}
@@ -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>
@@ -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>
)
@@ -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>
)
@@ -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>
</>
)
@@ -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&nbsp;{accessTooltip}</div>
<div className={roleColumnClassName}>Billing-only&nbsp;{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&nbsp;{accessTooltip}</div>
<div className={roleColumnClassName}>Billing-only&nbsp;{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>
</>
@@ -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
@@ -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>
</>
)
@@ -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>
</>
)
}}
@@ -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>
)
}
@@ -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>
)
}
@@ -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>
)
}
@@ -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>
</>
)
}}
@@ -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>
)
}
@@ -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>
@@ -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_>
@@ -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>
)
@@ -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
@@ -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>
</>
)
}
@@ -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>
</>
)
}
@@ -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>
-1
View File
@@ -24,7 +24,6 @@
.StripeElement {
width: 100%;
padding: 11px 15px 11px 0;
}
.ErrorMessage {
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -101,4 +101,4 @@ const WarningIcon: React.FC<SVGProps<SVGSVGElement> & StatusIconProps> = ({
)
}
export { WarningIcon, CriticalIcon }
export { WarningIcon, CriticalIcon, InfoIcon }
+1
View File
@@ -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%;
+1
View File
@@ -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%;
+4
View File
@@ -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%)"
+141 -154
View File
@@ -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'
+2 -1
View File
@@ -14,7 +14,8 @@ export default {
"bg-overlay",
"bg-overlay-hover",
"bg-muted",
"bg-button"
"bg-button",
"bg-dialog"
],
"border": [
"border",
+29
View File
@@ -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"}'
*/