Merge branch 'master' into fix/ahrefs-fixes
No files matched your search
@@ -1032,6 +1032,7 @@ h4.method-list-item-label {
|
||||
h4.method-list-item-label .method-list-item-label-name {
|
||||
font-family: var(--ifm-font-family-monospace);
|
||||
font-size: 0.8rem;
|
||||
margin-right: 4px;
|
||||
padding: var(--ifm-code-padding-vertical) 0;
|
||||
}
|
||||
h4.method-list-item-label .method-list-item-label-badge {
|
||||
|
||||
@@ -32,14 +32,14 @@ function walk(dir: string) {
|
||||
const list = fs.readdirSync(dir)
|
||||
list.forEach(function (file) {
|
||||
file = dir + '/' + file
|
||||
const stat = fs.statSync(file)
|
||||
if (stat && stat.isDirectory()) {
|
||||
/* Recurse into a subdirectory */
|
||||
results = results.concat(walk(file))
|
||||
} else {
|
||||
/* Is a file */
|
||||
results.push(file)
|
||||
}
|
||||
let slugs = []
|
||||
|
||||
fs.readdirSync(dir).forEach((file) => {
|
||||
let absolute = join(dir, file)
|
||||
if (fs.statSync(absolute).isDirectory()) {
|
||||
fs.readdirSync(absolute).forEach((subFile) => slugs.push(file.concat('/' + subFile)))
|
||||
}
|
||||
})
|
||||
})
|
||||
return results
|
||||
}
|
||||
@@ -28,7 +28,7 @@ const IncludedCheck = (props: any) => (
|
||||
const Minus = (props: any) => (
|
||||
<>
|
||||
<svg
|
||||
className="text-scale-1200 h-5 w-5 dark:text-white"
|
||||
className="text-scale-600 h-5 w-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
@@ -71,27 +71,29 @@ export const PricingTableRowDesktop = (props: any) => {
|
||||
return (
|
||||
<tr className="divide-scale-600 dark:divide-scale-400 divide-x">
|
||||
<th
|
||||
className="text-scale-1200 flex items-center px-6 py-5 text-left text-sm font-normal dark:text-white"
|
||||
className="text-scale-1200 flex items-center px-6 py-3 text-left text-sm font-normal dark:text-white"
|
||||
scope="row"
|
||||
>
|
||||
<span>{feat.title} </span>
|
||||
<span>{feat.title}</span>
|
||||
{feat.tooltip && (
|
||||
<span
|
||||
className="hover:text-scale-800 ml-2 cursor-pointer dark:hover:text-white"
|
||||
className="text-scale-900 hover:text-scale-1200 ml-2 cursor-pointer transition-colors"
|
||||
data-tip={feat.tooltip}
|
||||
>
|
||||
<IconHelpCircle size="small" />
|
||||
<IconHelpCircle size={14} strokeWidth={2} />
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
|
||||
{Object.values(feat.tiers).map((tier: any, i) => {
|
||||
return (
|
||||
<td key={i} className="px-6 py-5">
|
||||
<td key={i} className="px-6">
|
||||
{typeof tier === 'boolean' && tier === true ? (
|
||||
<IncludedCheck tier={tier} />
|
||||
) : typeof tier === 'boolean' && tier === false ? (
|
||||
<Minus tier={tier} />
|
||||
<div className="text-scale-900">
|
||||
<Minus tier={tier} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-scale-1200 block text-sm dark:text-white">{tier}</span>
|
||||
)}
|
||||
@@ -113,7 +115,7 @@ export const PricingTableRowMobile = (props: any) => {
|
||||
return (
|
||||
<>
|
||||
<table className="mt-8 w-full">
|
||||
<caption className="bg-scale-50 dark:bg-dark-900 border-scale-200 dark:border-scale-600 text-scale-1200 border-t px-4 py-3 text-left text-sm font-medium dark:text-white">
|
||||
<caption className="bg-scale-50 dark:bg-dark-900 border-scale-400 border-t px-4 py-3 text-left text-sm font-medium dark:text-white">
|
||||
<div className="flex items-center gap-2">
|
||||
{category.icon ? <ProductIcon icon={props.icon} /> : null}
|
||||
<span className="text-scale-1200 font-normal">{category.title}</span>
|
||||
@@ -129,16 +131,16 @@ export const PricingTableRowMobile = (props: any) => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-scale-600 divide-y">
|
||||
<tbody className="divide-scale-400 divide-y">
|
||||
{category.features.map((feat: any, i: number) => {
|
||||
return (
|
||||
<tr key={i} className="border-t">
|
||||
<th className="text-scale-1100 px-4 py-5 text-left text-sm font-normal" scope="row">
|
||||
<tr key={i} className="border-scale-400 border-t">
|
||||
<th className="text-scale-1100 px-4 py-3 text-left text-sm font-normal" scope="row">
|
||||
<span>
|
||||
<p>{feat.title}</p>
|
||||
</span>
|
||||
</th>
|
||||
<td className="py-5 pr-4 text-right">
|
||||
<td className="py-3 pr-4 text-right">
|
||||
{typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === true ? (
|
||||
<div className="inline-block">
|
||||
<IncludedCheck tier={tier} />
|
||||
|
||||
@@ -45,9 +45,17 @@
|
||||
"enterprise": "30 days Point in Time backup"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Point in time recovery",
|
||||
"tiers": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Pausing",
|
||||
"tooltip": "Unused projects that have no activity or API requests will be shut down. They can be reactivated by logging into the dashboard",
|
||||
"tooltip": "Projects that have no activity or API requests will be paused. They can be reactivated via the dashboard",
|
||||
"tiers": {
|
||||
"free": "Paused after 1 week inactivity",
|
||||
"pro": "Never",
|
||||
@@ -55,11 +63,11 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Transfer limits",
|
||||
"title": "Database egress",
|
||||
"tooltip": "Good news, only download operations are counted towards the limit",
|
||||
"tiers": {
|
||||
"free": "Up to 2GB",
|
||||
"pro": "50GB included, then $0.09 per GB",
|
||||
"pro": "50GB included per month, then $0.09 per GB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
}
|
||||
@@ -78,6 +86,15 @@
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "MAUs",
|
||||
"tooltip": "Monthly Active Users; a user that has made an API request in the last month.",
|
||||
"tiers": {
|
||||
"free": "Up to 50,000 MAUs",
|
||||
"pro": "100,000 included, then $0.00325 per MAU",
|
||||
"enterprise": "1,000,000 included, then $0.00325 per MAU"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Social OAuth providers",
|
||||
"tiers": {
|
||||
@@ -150,7 +167,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Transfer limits",
|
||||
"title": "Storage egress",
|
||||
"tooltip": "Good news, only download operations are counted towards the limit",
|
||||
"tiers": {
|
||||
"free": "Up to 2 GB",
|
||||
@@ -168,6 +185,7 @@
|
||||
},
|
||||
{
|
||||
"title": "Max file upload size",
|
||||
"tooltip": "You can change the upload size in the dashboard",
|
||||
"tiers": {
|
||||
"free": "Up to 50MB",
|
||||
"pro": "Up to 5GB",
|
||||
@@ -183,16 +201,16 @@
|
||||
{
|
||||
"title": "Runtime hours",
|
||||
"tiers": {
|
||||
"free": "100 hours",
|
||||
"pro": "1,000 hours",
|
||||
"free": "100 hours per month",
|
||||
"pro": "1,000 hours per month",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Invocations",
|
||||
"tiers": {
|
||||
"free": "500K",
|
||||
"pro": "2 Million",
|
||||
"free": "500K per month",
|
||||
"pro": "2 Million per month",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
@@ -308,7 +326,7 @@
|
||||
"title": "Metrics endpoint",
|
||||
"tiers": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"pro": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
@@ -343,6 +361,22 @@
|
||||
"pro": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Designated customer success engineer ",
|
||||
"tiers": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Security Questionnaire Help",
|
||||
"tiers": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"enterprise": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "10 (recommended)"
|
||||
"value": "30 (recommended)"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "50 (recommended)"
|
||||
"value": "75 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -61,7 +61,7 @@
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "500 (recommended)"
|
||||
"value": "150 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -80,7 +80,7 @@
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "1000 (recommended)"
|
||||
"value": "300 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -99,7 +99,7 @@
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "2000 (recommended)"
|
||||
"value": "600 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -113,12 +113,88 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "400 (recommended)"
|
||||
"value": "350 (recommended)"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "4000 (recommended)"
|
||||
"value": "1200 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "4XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$950" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "16-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "64 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 4,750 Mbps" },
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "420 (recommended)"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "2800 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "8XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$1,860" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "32-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "128 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 9,000 Mbps" },
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "450 (recommended)"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "5600 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "12XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$2,790" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "48-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "192 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 13,500 Mbps" },
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "480 (recommended)"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "8600 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "16XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$3,720" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "64-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "256 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 19,000 Mbps" },
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "500 (recommended)"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "11,600 (recommended)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -29,6 +29,30 @@ const components = {
|
||||
return <CodeBlock {...props} />
|
||||
},
|
||||
ImageGrid,
|
||||
img: (props: any) => {
|
||||
const classes = [
|
||||
'next-image--dynamic-fill',
|
||||
'from-brand-500 to-brand-500',
|
||||
'rounded border bg-gradient-to-r via-blue-500',
|
||||
]
|
||||
|
||||
return (
|
||||
<div
|
||||
className="
|
||||
next-image--dynamic-fill
|
||||
to-scale-400
|
||||
from-scale-500 rounded-md
|
||||
border bg-gradient-to-r
|
||||
"
|
||||
>
|
||||
<Image
|
||||
{...props}
|
||||
className="next-image--dynamic-fill to-brand-1000 from-brand-900 rounded-md border bg-gradient-to-r"
|
||||
layout="fill"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// plugins for next-mdx-remote
|
||||
|
||||
@@ -53,7 +53,7 @@ function Database() {
|
||||
openGraph={{
|
||||
title: meta_title,
|
||||
description: meta_description,
|
||||
url: `https://supabase.com/auth`,
|
||||
url: `https://supabase.com/database`,
|
||||
images: [
|
||||
{
|
||||
url: `https://supabase.com${basePath}/images/product/database/database-og.jpg`,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
// NOTE: this is a placeholder for our future Next.js marketing page. For now it redirects to our Next.js guide.
|
||||
|
||||
function Nextjs() {
|
||||
// base path for images
|
||||
const router = useRouter()
|
||||
const { basePath } = router
|
||||
|
||||
useEffect(() => {
|
||||
router.push('/docs/guides/with-nextjs')
|
||||
}, [])
|
||||
|
||||
const meta_title = 'Next.js Database with Auth, Realtime, Fiel Storage, and more.'
|
||||
const meta_description =
|
||||
'Supabase is the fastest way to build performant Next.js apps with Authentication & User Management, Realtime, File Storage on top of PostgreSQL Databases.'
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title={meta_title}
|
||||
description={meta_description}
|
||||
openGraph={{
|
||||
title: meta_title,
|
||||
description: meta_description,
|
||||
url: `https://supabase.com/nextjs`,
|
||||
images: [
|
||||
{
|
||||
url: `https://supabase.com${basePath}/images/product/database/database-og.jpg`,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Nextjs
|
||||
@@ -0,0 +1,3 @@
|
||||
import Nextjs from './Nextjs'
|
||||
|
||||
export default Nextjs
|
||||
@@ -1,5 +1,27 @@
|
||||
Pricing Changelog
|
||||
|
||||
### 16 June 2022
|
||||
----
|
||||
|
||||
Auth
|
||||
- New item “MAUs (Monthly Active Users)”
|
||||
Security & Compliance
|
||||
- New item: “Security questionnaire help” for enterprise
|
||||
- New item - “Metrics Endpoint” for enterprise and pro tier users
|
||||
Database Compute Add ons
|
||||
- New item: “4XL”
|
||||
- New item: “8XL”
|
||||
- New item: “12XL”
|
||||
- New item: “16XL”
|
||||
Database
|
||||
- New item: “Point in time recovery” for enterprise
|
||||
Clarifications
|
||||
- General - Clearer language that we charge on a per project per month
|
||||
- Storage - Uploads for Pro, changed from 3GB → 5GB in title card
|
||||
- Storage - file upload size - clarified that it can be changed in dashboard
|
||||
- All products - “Transfer” renamed to “Egress”
|
||||
- Database compute add-ons - Number of connections more accurate
|
||||
- Storage - Free tier egress per month clarified
|
||||
|
||||
### 31 March 2022
|
||||
----
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function IndexPage() {
|
||||
const tiers = [
|
||||
{
|
||||
name: 'Free',
|
||||
costUnit: '/month per project',
|
||||
href: 'https://app.supabase.com/new/new-project',
|
||||
priceMonthly: 0,
|
||||
warning: 'Limit of 2 free projects',
|
||||
@@ -39,6 +40,7 @@ export default function IndexPage() {
|
||||
'Up to 2GB bandwidth',
|
||||
'Up to 50MB file uploads',
|
||||
'Social OAuth providers',
|
||||
'50,000 monthly active users',
|
||||
'Up to 500K Edge Function invocations',
|
||||
'1-day log retention',
|
||||
'Community support',
|
||||
@@ -49,16 +51,17 @@ export default function IndexPage() {
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
costUnit: '/month per project',
|
||||
href: 'https://app.supabase.com/new/new-project',
|
||||
from: true,
|
||||
priceMonthly: 25,
|
||||
warning: 'per project',
|
||||
description: 'For production applications with the option to scale.',
|
||||
features: [
|
||||
'8GB database & 100GB file storage',
|
||||
'50GB bandwidth',
|
||||
'3GB file uploads',
|
||||
'5GB file uploads',
|
||||
'Social OAuth providers',
|
||||
'100,000 monthly active users',
|
||||
'2M Edge Function invocations',
|
||||
'Daily backups',
|
||||
'7-day log retention',
|
||||
@@ -181,21 +184,16 @@ export default function IndexPage() {
|
||||
border
|
||||
"
|
||||
>
|
||||
<div className="dark:bg-scale-300 h-48 bg-white px-8 pt-6">
|
||||
<h3
|
||||
className="
|
||||
text-scale-1200
|
||||
mb-2
|
||||
inline-flex
|
||||
rounded-full
|
||||
text-sm
|
||||
font-normal
|
||||
tracking-wide
|
||||
lg:text-xl"
|
||||
id="tier-standard"
|
||||
>
|
||||
{tier.name}
|
||||
</h3>
|
||||
<div className="dark:bg-scale-300 h-[220px] bg-white px-8 pt-6">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<h3 className="text-scale-1200 text-xl font-medium">{tier.name}</h3>
|
||||
{tier.warning && (
|
||||
<div className="bg-scale-400 text-scale-1100 rounded-md bg-opacity-30 py-0.5 px-2 text-xs">
|
||||
{tier.warning}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-scale-1100 my-4 h-[40px] text-sm">{tier.description}</p>
|
||||
<div
|
||||
className="
|
||||
text-scale-1200 flex items-baseline
|
||||
@@ -207,44 +205,47 @@ export default function IndexPage() {
|
||||
"
|
||||
>
|
||||
{tier.priceMonthly !== undefined ? (
|
||||
<div className="flex items-end gap-2">
|
||||
{tier.from && (
|
||||
<span className="text-base font-medium xl:text-xl">From</span>
|
||||
)}
|
||||
<div>
|
||||
<span>${tier.priceMonthly}</span>
|
||||
<span className="text-scale-900 ml-1 text-2xl font-medium">/mo</span>
|
||||
</div>
|
||||
{tier.warning && (
|
||||
<div className="bg-brand-300 text-brand-1000 mt-2 rounded-md bg-opacity-30 px-2 py-1 text-xs">
|
||||
{tier.warning}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-end gap-2">
|
||||
<div>
|
||||
<span>
|
||||
{typeof tier.priceMonthly !== 'string' && '$'}
|
||||
{tier.priceMonthly}
|
||||
</span>
|
||||
<span className="text-scale-900 ml-1 text-xl font-normal">
|
||||
{tier.costUnit}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{tier.from && (
|
||||
<span className="text-scale-900 block text-sm">
|
||||
+ any additional usage
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<span>Contact Us</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-scale-1100 mt-4 text-base">{tier.description}</p>
|
||||
</div>
|
||||
<div
|
||||
className="
|
||||
dark:border-scale-400 bg-scale-100 dark:bg-scale-300 flex
|
||||
dark:border-scale-400 dark:bg-scale-300 flex h-full
|
||||
|
||||
h-full
|
||||
flex-1
|
||||
flex-col
|
||||
|
||||
justify-between
|
||||
space-y-6
|
||||
|
||||
space-y-6
|
||||
border-t
|
||||
|
||||
bg-white
|
||||
px-8
|
||||
|
||||
py-6
|
||||
"
|
||||
>
|
||||
{tier.preface && <p className="text-scale-1200 text-base">{tier.preface}</p>}
|
||||
{tier.preface && <p className="text-scale-1100 text-sm">{tier.preface}</p>}
|
||||
{/* <p className="text-scale-900 text-sm">Included with plan:</p> */}
|
||||
<ul role="list" className="dark:divide-scale-400 divide-y">
|
||||
{tier.features.map((feature) => (
|
||||
@@ -645,16 +646,22 @@ export default function IndexPage() {
|
||||
<div className="mt-16">
|
||||
{/* @ts-ignore */}
|
||||
<Accordion
|
||||
type="bordered"
|
||||
type="default"
|
||||
openBehaviour="multiple"
|
||||
size="medium"
|
||||
className="text-scale-900 dark:text-white"
|
||||
>
|
||||
{pricingFaq.map((faq, i) => {
|
||||
return (
|
||||
<Accordion.Item key={i} header={faq.question} id={`faq--${i.toString()}`}>
|
||||
<ReactMarkdown className="text-scale-1100">{faq.answer}</ReactMarkdown>
|
||||
</Accordion.Item>
|
||||
<div className="border-b pb-3">
|
||||
<Accordion.Item
|
||||
key={i}
|
||||
header={<span className="text-scale-1200">{faq.question}</span>}
|
||||
id={`faq--${i.toString()}`}
|
||||
>
|
||||
<ReactMarkdown className="text-scale-900">{faq.answer}</ReactMarkdown>
|
||||
</Accordion.Item>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</Accordion>
|
||||
|
||||
@@ -223,3 +223,22 @@ table {
|
||||
.sbui-typography a {
|
||||
@apply break-words;
|
||||
}
|
||||
|
||||
/*
|
||||
* sets the image in @Next/Image components
|
||||
* to respect the height of the content
|
||||
*
|
||||
*/
|
||||
.next-image--dynamic-fill {
|
||||
width: 100%;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.next-image--dynamic-fill > span {
|
||||
position: relative !important;
|
||||
}
|
||||
.next-image--dynamic-fill img {
|
||||
object-fit: contain;
|
||||
width: 100% !important;
|
||||
position: relative !important;
|
||||
height: unset !important;
|
||||
}
|
||||
@@ -9,12 +9,14 @@ export default function IndexPage() {
|
||||
<div className="w-full h-full bg-gray-300">
|
||||
{!user ? (
|
||||
<div className="w-full h-full flex justify-center items-center p-4">
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
socialButtonSize="xlarge"
|
||||
/>
|
||||
<div>
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
socialButtonSize="xlarge"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -31,7 +31,9 @@ const PolicyEditor: FC<Props> = ({
|
||||
const operation = policyFormFields?.command ?? ''
|
||||
const definition = policyFormFields?.definition ?? ''
|
||||
const check = policyFormFields?.check ?? ''
|
||||
const selectedRoles = policyFormFields?.roles ?? []
|
||||
|
||||
// Filter out default public role (public if no roles selected)
|
||||
const selectedRoles = (policyFormFields?.roles ?? []).filter((role: string) => role !== 'public')
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { FC } from 'react'
|
||||
import { Button, Typography } from '@supabase/ui'
|
||||
|
||||
interface Props {
|
||||
organization: any
|
||||
}
|
||||
|
||||
const AWSMarketplaceSubscription: FC<Props> = ({ organization }) => {
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<Typography.Text>
|
||||
This organization is subscribed via AWS Marketplace. The subscription expires on{' '}
|
||||
{organization.aws_marketplace.Entitlements[0].ExpirationDate.slice(0, 10)}.
|
||||
</Typography.Text>
|
||||
|
||||
<Button>
|
||||
<a
|
||||
href="https://aws.amazon.com/marketplace/saas/ordering?productId=dc498450-cecf-44c2-8c99-c3c13f16e70a&offerId=d1htt16t3ygx5brtwhooms0ei"
|
||||
target="_blank"
|
||||
>
|
||||
Manage AWS Marketplace subscription
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AWSMarketplaceSubscription
|
||||
@@ -4,7 +4,6 @@ import { useStore } from 'hooks'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { get } from 'lib/common/fetch'
|
||||
|
||||
import AWSMarketplaceSubscription from './AWSMarketplaceSubscription'
|
||||
import ProjectsSummary from './ProjectsSummary'
|
||||
import CreditBalance from './CreditBalance'
|
||||
import PaymentMethods from './PaymentMethods'
|
||||
@@ -94,33 +93,27 @@ const BillingSettings: FC<Props> = ({ organization, projects = [] }) => {
|
||||
|
||||
return (
|
||||
<article className="container my-4 max-w-4xl space-y-8">
|
||||
{organization.aws_marketplace ? (
|
||||
<AWSMarketplaceSubscription organization={organization} />
|
||||
) : (
|
||||
<>
|
||||
<div className="space-y-8">
|
||||
<ProjectsSummary projects={projects} />
|
||||
<CreditBalance balance={balance} isCredit={isCredit} isDebt={isDebt} />
|
||||
<PaymentMethods
|
||||
loading={isLoadingCustomer || isLoadingPaymentMethods}
|
||||
defaultPaymentMethod={defaultPaymentMethod}
|
||||
paymentMethods={paymentMethods || []}
|
||||
onDefaultMethodUpdated={setCustomer}
|
||||
onPaymentMethodsDeleted={() => getPaymentMethods()}
|
||||
/>
|
||||
<BillingAddress
|
||||
loading={isLoadingCustomer}
|
||||
address={customer?.address ?? {}}
|
||||
onAddressUpdated={(address: any) => setCustomer({ ...customer, address })}
|
||||
/>
|
||||
<TaxID
|
||||
loading={isLoadingTaxIds}
|
||||
taxIds={taxIds || []}
|
||||
onTaxIdsUpdated={(ids: any) => setTaxIds(ids)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="space-y-8">
|
||||
<ProjectsSummary projects={projects} />
|
||||
<CreditBalance balance={balance} isCredit={isCredit} isDebt={isDebt} />
|
||||
<PaymentMethods
|
||||
loading={isLoadingCustomer || isLoadingPaymentMethods}
|
||||
defaultPaymentMethod={defaultPaymentMethod}
|
||||
paymentMethods={paymentMethods || []}
|
||||
onDefaultMethodUpdated={setCustomer}
|
||||
onPaymentMethodsDeleted={() => getPaymentMethods()}
|
||||
/>
|
||||
<BillingAddress
|
||||
loading={isLoadingCustomer}
|
||||
address={customer?.address ?? {}}
|
||||
onAddressUpdated={(address: any) => setCustomer({ ...customer, address })}
|
||||
/>
|
||||
<TaxID
|
||||
loading={isLoadingTaxIds}
|
||||
taxIds={taxIds || []}
|
||||
onTaxIdsUpdated={(ids: any) => setTaxIds(ids)}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@ import {
|
||||
ExtensionsUpgrade,
|
||||
Notification,
|
||||
NotificationName,
|
||||
PostgresqlUpgradeData,
|
||||
ProjectExceedingTierLimitData,
|
||||
ProjectUpdateData,
|
||||
ServerUpgrade,
|
||||
ServiceUpgrade,
|
||||
ViolatedLimit,
|
||||
@@ -15,17 +12,16 @@ import {
|
||||
|
||||
export const formatNotificationText = (project: Project, notification: Notification) => {
|
||||
const projectName = project.name
|
||||
const notificationName = notification.notification_name
|
||||
|
||||
if (notificationName === NotificationName.ProjectExceedingTierLimit) {
|
||||
const { violations } = notification.data as ProjectExceedingTierLimitData
|
||||
if (notification.data.name === NotificationName.ProjectExceedingTierLimit) {
|
||||
const { violations } = notification.data
|
||||
const violationsText = violations
|
||||
.map((violation: ViolatedLimit) => violation.dimension as string)
|
||||
.reduce((a: string, b: string) => `${a}, ${b}`)
|
||||
|
||||
return `Your project "${projectName}" has exceeded its limits in the following areas: ${violationsText}.`
|
||||
} else if (notificationName === NotificationName.PostgresqlUpgradeAvailable) {
|
||||
const { upgrade_type, additional } = notification.data as PostgresqlUpgradeData
|
||||
} else if (notification.data.name === NotificationName.PostgresqlUpgradeAvailable) {
|
||||
const { upgrade_type, additional } = notification.data
|
||||
|
||||
if (upgrade_type === 'postgresql-server') {
|
||||
const { version_to } = additional as ServerUpgrade
|
||||
@@ -35,24 +31,26 @@ export const formatNotificationText = (project: Project, notification: Notificat
|
||||
return `New version of "${name}" (${version_to}) is now available for project "${projectName}".`
|
||||
}
|
||||
return ''
|
||||
} else if (notificationName === NotificationName.PostgresqlUpgradeCompleted) {
|
||||
const { upgrade_type, additional } = notification.data as PostgresqlUpgradeData
|
||||
} else if (notification.data.name === NotificationName.PostgresqlUpgradeCompleted) {
|
||||
const { upgrade_type, additional } = notification.data
|
||||
|
||||
if (upgrade_type === 'postgresql-server') {
|
||||
const { version_to } = additional as ServerUpgrade
|
||||
return `Postgres (${version_to}) has been successfully updated to ${version_to} for project "${projectName}".`
|
||||
} else if (upgrade_type === 'extensions') {
|
||||
const { name, version_to } = additional as ExtensionsUpgrade
|
||||
return `The extension "${name}" has been successfully updated to ${version_to} for project "${projectName}`
|
||||
return `The extension "${name}" has been successfully updated to ${version_to} for project "${projectName}".`
|
||||
}
|
||||
} else if (notificationName === 'project.update-completed') {
|
||||
const { upgrades } = notification.data as ProjectUpdateData
|
||||
} else if (notification.data.name === NotificationName.ProjectUpdateCompleted) {
|
||||
const { upgrades } = notification.data
|
||||
const upgradesText = upgrades
|
||||
.map((upgrade: ServiceUpgrade) => upgrade.name)
|
||||
.reduce((a: string, b: string) => `${a}, ${b}`)
|
||||
return `The following services have been successfully updated for project "${projectName}": ${upgradesText}`
|
||||
return `The following services have been successfully updated for project "${projectName}": ${upgradesText}.`
|
||||
} else if (notification.data.name === NotificationName.ProjectInformational) {
|
||||
return notification.data.message
|
||||
} else {
|
||||
return `Unknown notification type: ${notification.notification_name} - Please reach out to support for more information`
|
||||
return `Unknown notification type: ${notification.notification_name} - Please reach out to support for more information.`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +63,8 @@ export const formatNotificationCTAText = (availableActions: Action[]) => {
|
||||
return 'Restart your project to get the latest updates.'
|
||||
case ActionType.UpgradeProjectToPro:
|
||||
return 'Upgrade your project to ensure continued availability.'
|
||||
case ActionType.PgBouncerRestart:
|
||||
return 'Restart your connection pooler to get the latest updates.'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC, Fragment, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, IconBell, Popover } from '@supabase/ui'
|
||||
import { Notification, NotificationStatus } from '@supabase/shared-types/out/notifications'
|
||||
import { Notification, NotificationStatus, ActionType } from '@supabase/shared-types/out/notifications'
|
||||
|
||||
import { Project } from 'types'
|
||||
import { useNotifications, useStore } from 'hooks'
|
||||
@@ -44,12 +44,21 @@ const NotificationsPopover: FC<Props> = () => {
|
||||
if (!projectToRestart || !targetNotification) return
|
||||
|
||||
const { id } = targetNotification
|
||||
|
||||
const { id: projectId, ref, region } = projectToRestart
|
||||
const serviceNamesByActionName: Record<string, string> = {
|
||||
[ActionType.PgBouncerRestart]: 'pgbouncer',
|
||||
[ActionType.SchedulePostgresRestart]: 'postgresql'
|
||||
}
|
||||
const services: string[] = targetNotification.meta.actions_available
|
||||
.map((action) => action.action_type)
|
||||
.filter((actionName) => Object.keys(serviceNamesByActionName).indexOf(actionName) !== -1)
|
||||
.map((actionName) => serviceNamesByActionName[actionName])
|
||||
const { error } = await post(`${API_URL}/projects/${ref}/restart-services`, {
|
||||
restartRequest: {
|
||||
region,
|
||||
source_notification_id: id,
|
||||
services: ['postgresql'],
|
||||
services: services,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -61,7 +70,7 @@ const NotificationsPopover: FC<Props> = () => {
|
||||
})
|
||||
} else {
|
||||
app.onProjectPostgrestStatusUpdated(projectId, 'OFFLINE')
|
||||
ui.setNotification({ category: 'success', message: `Restarting project` })
|
||||
ui.setNotification({ category: 'success', message: `Restarting services` })
|
||||
router.push(`/project/${ref}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FC, ReactNode, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
import Error from 'components/ui/Error'
|
||||
import { useSqlEditorStore, SqlEditorContext } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import ProjectLayout from '../ProjectLayout/ProjectLayout'
|
||||
import SQLEditorMenu from './SQLEditorMenu'
|
||||
|
||||
@@ -12,26 +13,36 @@ interface Props {
|
||||
}
|
||||
|
||||
const SQLEditorLayout: FC<Props> = ({ title, children }) => {
|
||||
const { content } = useStore()
|
||||
const { ui, content, meta } = useStore()
|
||||
const { profile: user } = ui
|
||||
|
||||
const sqlEditorStore = useSqlEditorStore(ui.selectedProject?.ref, meta)
|
||||
|
||||
useEffect(() => {
|
||||
content.load()
|
||||
}, [])
|
||||
if (sqlEditorStore) {
|
||||
// this calls content.load() for us, as well as loading tabs
|
||||
sqlEditorStore.loadRemotePersistentData(content, user?.id)
|
||||
}
|
||||
}, [sqlEditorStore])
|
||||
|
||||
return content.error ? (
|
||||
<ProjectLayout>
|
||||
<Error error={content.error} />
|
||||
</ProjectLayout>
|
||||
) : (
|
||||
<ProjectLayout
|
||||
isLoading={content.isLoading}
|
||||
title={title || 'SQL'}
|
||||
product="SQL Editor"
|
||||
productMenu={<SQLEditorMenu />}
|
||||
>
|
||||
{children}
|
||||
</ProjectLayout>
|
||||
return (
|
||||
<SqlEditorContext.Provider value={sqlEditorStore}>
|
||||
{content.error ? (
|
||||
<ProjectLayout>
|
||||
<Error error={content.error} />
|
||||
</ProjectLayout>
|
||||
) : (
|
||||
<ProjectLayout
|
||||
isLoading={content.isLoading || sqlEditorStore === null}
|
||||
title={title || 'SQL'}
|
||||
product="SQL Editor"
|
||||
productMenu={<SQLEditorMenu />}
|
||||
>
|
||||
{children}
|
||||
</ProjectLayout>
|
||||
)}
|
||||
</SqlEditorContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(SQLEditorLayout)
|
||||
export default withAuth(observer(SQLEditorLayout))
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {
|
||||
Button,
|
||||
@@ -12,19 +11,16 @@ import {
|
||||
IconLoader,
|
||||
IconTrash,
|
||||
IconChevronDown,
|
||||
Loading,
|
||||
Modal,
|
||||
IconEdit2,
|
||||
} from '@supabase/ui'
|
||||
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { useStore } from 'hooks'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
import { useOptimisticSqlSnippetCreate, useStore } from 'hooks'
|
||||
import QueryTab from 'localStores/sqlEditor/QueryTab'
|
||||
import { useSqlStore, TAB_TYPES } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
|
||||
import RenameQuery from 'components/to-be-cleaned/SqlEditor/RenameQuery'
|
||||
import { createSqlSnippet } from 'components/to-be-cleaned/SqlEditor/SqlEditor.utils'
|
||||
import ConfirmationModal from 'components/ui/ConfirmationModal'
|
||||
import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem'
|
||||
|
||||
@@ -46,11 +42,12 @@ const OpenQueryItem = observer(({ tabInfo }: { tabInfo: QueryTab }) => {
|
||||
})
|
||||
|
||||
const DropdownMenu = observer(({ tabInfo }: { tabInfo: QueryTab }) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const {
|
||||
ui: { profile: user },
|
||||
content: contentStore,
|
||||
} = useStore()
|
||||
|
||||
const sqlEditorStore: any = useSqlStore()
|
||||
const contentStore: any = useProjectContentStore(ref)
|
||||
|
||||
const [tabId, setTabId] = useState('')
|
||||
const [renameModalOpen, setRenameModalOpen] = useState(false)
|
||||
@@ -109,9 +106,14 @@ const DropdownMenu = observer(({ tabInfo }: { tabInfo: QueryTab }) => {
|
||||
buttonLabel="Confirm"
|
||||
visible={deleteModalOpen}
|
||||
onSelectConfirm={async () => {
|
||||
await contentStore.del(id)
|
||||
await contentStore.load()
|
||||
sqlEditorStore.closeTab(id)
|
||||
|
||||
await contentStore.del(id)
|
||||
|
||||
sqlEditorStore.loadTabs(
|
||||
sqlEditorStore.tabsFromContentStore(contentStore, user?.id),
|
||||
false
|
||||
)
|
||||
}}
|
||||
onSelectCancel={() => setDeleteModalOpen(false)}
|
||||
>
|
||||
@@ -124,39 +126,11 @@ const DropdownMenu = observer(({ tabInfo }: { tabInfo: QueryTab }) => {
|
||||
})
|
||||
|
||||
const SideBarContent = observer(() => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
const { ui } = useStore()
|
||||
const { profile: user } = ui
|
||||
|
||||
const sqlEditorStore: any = useSqlStore()
|
||||
const contentStore: any = useProjectContentStore(ref)
|
||||
|
||||
const [filterString, setFilterString] = useState('')
|
||||
const [loadingNewQuery, setLoadingNewQuery] = useState(false)
|
||||
|
||||
async function handleNewQuery() {
|
||||
try {
|
||||
setLoadingNewQuery(true)
|
||||
// create new sql snippet, this also reloads the project_content store
|
||||
// @ts-ignore
|
||||
const snippet = await createSqlSnippet({ router })
|
||||
|
||||
// reload the local sqlEditorStore
|
||||
await sqlEditorStore.loadRemotePersistentData(contentStore, user?.id)
|
||||
|
||||
// select tab with new snippet
|
||||
if (snippet) sqlEditorStore.selectTab(snippet.id)
|
||||
setLoadingNewQuery(false)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to create new query: ${error.message}`,
|
||||
})
|
||||
setLoadingNewQuery(false)
|
||||
}
|
||||
}
|
||||
const handleNewQuery = useOptimisticSqlSnippetCreate()
|
||||
|
||||
const getStartedTabs = (sqlEditorStore?.tabs ?? []).filter((tab: any) => {
|
||||
return tab.type === TAB_TYPES.WELCOME
|
||||
@@ -191,7 +165,6 @@ const SideBarContent = observer(() => {
|
||||
type="default"
|
||||
style={{ justifyContent: 'start' }}
|
||||
onClick={() => handleNewQuery()}
|
||||
loading={loadingNewQuery}
|
||||
>
|
||||
New query
|
||||
</Button>
|
||||
@@ -237,37 +210,35 @@ const SideBarContent = observer(() => {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Loading active={contentStore.isInitialized}>
|
||||
<div className="space-y-6 px-3">
|
||||
{favouriteTabs.length >= 1 && (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Favorites" />
|
||||
<div className="space-y-1">
|
||||
{favouriteTabs.map((tabInfo: any) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <OpenQueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
</div>
|
||||
<div className="space-y-6 px-3">
|
||||
{favouriteTabs.length >= 1 && (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Favorites" />
|
||||
<div className="space-y-1">
|
||||
{favouriteTabs.map((tabInfo: any) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <OpenQueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{queryTabs.length >= 1 && (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="SQL snippets" />
|
||||
<div className="space-y-1">
|
||||
{queryTabs.map((tabInfo: any) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <OpenQueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{queryTabs.length >= 1 && (
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="SQL snippets" />
|
||||
<div className="space-y-1">
|
||||
{queryTabs.map((tabInfo: any) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <OpenQueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{filterString.length > 0 && favouriteTabs.length === 0 && queryTabs.length === 0 && (
|
||||
<div className="px-4">
|
||||
<p className="text-sm">No queries found</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Loading>
|
||||
</div>
|
||||
)}
|
||||
{filterString.length > 0 && favouriteTabs.length === 0 && queryTabs.length === 0 && (
|
||||
<div className="px-4">
|
||||
<p className="text-sm">No queries found</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Menu>
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import React from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { useSqlStore } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
import { Button, Form, Input, Modal } from '@supabase/ui'
|
||||
|
||||
const RenameQuery = observer(({ tabId, onComplete, visible, onCancel }) => {
|
||||
const { ui } = useStore()
|
||||
const { ui, content: contentStore } = useStore()
|
||||
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
const contentStore = useProjectContentStore(ref)
|
||||
const sqlEditorStore = useSqlStore()
|
||||
const model = prepareModel()
|
||||
|
||||
@@ -27,10 +21,6 @@ const RenameQuery = observer(({ tabId, onComplete, visible, onCancel }) => {
|
||||
|
||||
async function onRename(model) {
|
||||
try {
|
||||
/*
|
||||
* old localStorage save
|
||||
*/
|
||||
sqlEditorStore.renameQuery(tabId, model)
|
||||
/*
|
||||
* new main db save
|
||||
*/
|
||||
@@ -38,7 +28,12 @@ const RenameQuery = observer(({ tabId, onComplete, visible, onCancel }) => {
|
||||
name: model.name,
|
||||
description: model.desc,
|
||||
})
|
||||
await contentStore.load()
|
||||
|
||||
/*
|
||||
* old localStorage save
|
||||
*/
|
||||
sqlEditorStore.renameQuery(tabId, model)
|
||||
|
||||
if (onComplete) onComplete()
|
||||
return Promise.resolve()
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { UserContent } from 'types'
|
||||
import { SqlSnippets, UserContent } from 'types'
|
||||
|
||||
export const SQL_SNIPPET_SCHEMA_VERSION = '1.0'
|
||||
|
||||
export const NEW_SQL_SNIPPET_SKELETON: UserContent = {
|
||||
export const NEW_SQL_SNIPPET_SKELETON: UserContent<SqlSnippets.Content> = {
|
||||
name: 'New Query',
|
||||
description: '',
|
||||
type: 'sql',
|
||||
|
||||
@@ -1,53 +1,23 @@
|
||||
import { UserContent } from 'types'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
import { SqlSnippets, UserContent } from 'types'
|
||||
import { NEW_SQL_SNIPPET_SKELETON } from './SqlEditor.constants'
|
||||
|
||||
/* Creates a new SQL snippet with a basic skeleton */
|
||||
export const createSqlSnippet = async ({
|
||||
router,
|
||||
sql,
|
||||
export const createSqlSnippetSkeleton = ({
|
||||
name,
|
||||
sql,
|
||||
owner_id,
|
||||
}: {
|
||||
router: any
|
||||
sql: any
|
||||
name: any
|
||||
}) => {
|
||||
const { ref } = router.query
|
||||
const contentStore = useProjectContentStore(ref)
|
||||
const skeleton: UserContent = {
|
||||
name?: string
|
||||
sql?: string
|
||||
owner_id?: number
|
||||
} = {}): UserContent<SqlSnippets.Content> => {
|
||||
return {
|
||||
...NEW_SQL_SNIPPET_SKELETON,
|
||||
...(name && { name }),
|
||||
...(owner_id && { owner_id }),
|
||||
content: {
|
||||
...NEW_SQL_SNIPPET_SKELETON.content,
|
||||
content_id: '',
|
||||
sql: sql,
|
||||
sql: sql ?? '',
|
||||
},
|
||||
}
|
||||
|
||||
const { data: sqlSnippet, error } = await contentStore.create(skeleton)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
// reload store data
|
||||
await contentStore.load()
|
||||
return sqlSnippet[0]
|
||||
}
|
||||
|
||||
/*
|
||||
* deleteSqlSnippet()
|
||||
*
|
||||
* Deletes a SQL snippet
|
||||
*
|
||||
*/
|
||||
export const deleteSqlSnippet = async ({ router, id }: { router: any; id: any }) => {
|
||||
const { ref } = router.query
|
||||
const contentStore = useProjectContentStore(ref)
|
||||
|
||||
const { data: delReport, error } = await contentStore.del(id)
|
||||
if (error) throw error
|
||||
|
||||
// reload store data
|
||||
await contentStore.load()
|
||||
|
||||
return delReport
|
||||
}
|
||||
@@ -1,32 +1,34 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import DataGrid from '@supabase/react-data-grid'
|
||||
import Split from 'react-split'
|
||||
import Editor from '@monaco-editor/react'
|
||||
import DataGrid from '@supabase/react-data-grid'
|
||||
import { toJS } from 'mobx'
|
||||
import { CSVLink } from 'react-csv'
|
||||
import { debounce } from 'lodash'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
IconHeart,
|
||||
Dropdown,
|
||||
IconAlertCircle,
|
||||
IconCheck,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconHeart,
|
||||
IconLoader,
|
||||
IconRefreshCcw,
|
||||
Typography,
|
||||
Dropdown,
|
||||
} from '@supabase/ui'
|
||||
import Editor from '@monaco-editor/react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
import { copyToClipboard, timeout } from 'lib/helpers'
|
||||
import { useKeyboardShortcuts, useStore, useWindowDimensions, usePrevious } from 'hooks'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import { copyToClipboard, timeout } from 'lib/helpers'
|
||||
import { useSqlStore, UTILITY_TAB_TYPES } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
|
||||
import { SQL_SNIPPET_SCHEMA_VERSION } from './SqlEditor.constants'
|
||||
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useStore } from 'hooks'
|
||||
import { debounce } from 'lodash'
|
||||
import { useWindowDimensions, useKeyboardShortcuts } from 'hooks'
|
||||
|
||||
const TabSqlQuery = observer(() => {
|
||||
const { content: contentStore } = useStore()
|
||||
const sqlEditorStore = useSqlStore()
|
||||
const { height: screenHeight } = useWindowDimensions()
|
||||
const snapOffset = 50
|
||||
@@ -49,8 +51,23 @@ const TabSqlQuery = observer(() => {
|
||||
sqlEditorStore.activeTab.setSplitSizes(sizes)
|
||||
}
|
||||
|
||||
async function updateSqlSnippet(value) {
|
||||
if (sqlEditorStore.activeTab) {
|
||||
await contentStore.updateSql(sqlEditorStore.activeTab.id, {
|
||||
content: {
|
||||
schema_version: SQL_SNIPPET_SCHEMA_VERSION,
|
||||
content_id: sqlEditorStore.activeTab.id,
|
||||
sql: value,
|
||||
favorite: sqlEditorStore.activeTab.favorite,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
console.warn('No active tab found while updating SQL snippet')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex h-full flex-col">
|
||||
<Split
|
||||
style={{ height: '100%' }}
|
||||
direction="vertical"
|
||||
@@ -62,9 +79,12 @@ const TabSqlQuery = observer(() => {
|
||||
collapsed={sqlEditorStore.activeTab.utilityTabHeight == 0 ? 1 : undefined}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<MonacoEditor error={sqlEditorStore.activeTab.sqlQueryError} />
|
||||
<MonacoEditor
|
||||
error={sqlEditorStore.activeTab.sqlQueryError}
|
||||
updateSqlSnippet={updateSqlSnippet}
|
||||
/>
|
||||
<div>
|
||||
<UtilityPanel />
|
||||
<UtilityPanel updateSqlSnippet={updateSqlSnippet} />
|
||||
</div>
|
||||
</Split>
|
||||
</div>
|
||||
@@ -72,12 +92,7 @@ const TabSqlQuery = observer(() => {
|
||||
})
|
||||
export default TabSqlQuery
|
||||
|
||||
const MonacoEditor = ({ error }) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
const contentStore = useProjectContentStore(ref)
|
||||
|
||||
const MonacoEditor = ({ error, updateSqlSnippet }) => {
|
||||
const sqlEditorStore = useSqlStore()
|
||||
const editorRef = useRef(null)
|
||||
const monacoRef = useRef(null)
|
||||
@@ -154,6 +169,9 @@ const MonacoEditor = ({ error }) => {
|
||||
editor?.focus()
|
||||
}
|
||||
|
||||
// changes are stored in debouncer before running persistData()
|
||||
let debounceUpdateSqlSnippet = debounce((value) => updateSqlSnippet(value), 1500)
|
||||
|
||||
async function handleEditorChange(value) {
|
||||
// update sqlEditorStore with new value immediately
|
||||
// this is so any SQL run will be whatever is currently in monaco editor
|
||||
@@ -163,29 +181,8 @@ const MonacoEditor = ({ error }) => {
|
||||
debounceUpdateSqlSnippet(value)
|
||||
}
|
||||
|
||||
// changes are stored in debouncer before running persistData()
|
||||
let debounceUpdateSqlSnippet = debounce((value) => updateSqlSnippet(value), 1500)
|
||||
|
||||
async function updateSqlSnippet(value) {
|
||||
//
|
||||
// this will need to be refactored but for now
|
||||
// we still use the old sqlEditorStore to handle db updates
|
||||
if (sqlEditorStore.activeTab) {
|
||||
await contentStore.updateSql(sqlEditorStore.activeTab.id, {
|
||||
content: {
|
||||
schema_version: SQL_SNIPPET_SCHEMA_VERSION,
|
||||
content_id: sqlEditorStore.activeTab.id,
|
||||
sql: value,
|
||||
favorite: sqlEditorStore.activeTab.favorite,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
console.warn('No active tab found while updating SQL snippet')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-grow overflow-y-auto border-b dark:border-dark">
|
||||
<div className="dark:border-dark flex-grow overflow-y-auto border-b">
|
||||
<Editor
|
||||
className="monaco-editor"
|
||||
theme={'supabase'}
|
||||
@@ -208,15 +205,15 @@ const MonacoEditor = ({ error }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const UtilityPanel = observer(() => {
|
||||
const UtilityPanel = observer(({ updateSqlSnippet }) => {
|
||||
const sqlEditorStore = useSqlStore()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between overflow-visible px-6 py-2">
|
||||
<ResultsDropdown />
|
||||
<div className="inline-flex justify-end">
|
||||
<UtilityActions />
|
||||
<div className="inline-flex items-center justify-end">
|
||||
<UtilityActions updateSqlSnippet={updateSqlSnippet} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-0 pt-0 pb-0">
|
||||
@@ -271,7 +268,101 @@ const ResultsDropdown = observer(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const UtilityActions = observer(() => {
|
||||
const SavingIndicator = observer(({ updateSqlSnippet }) => {
|
||||
const { content } = useStore()
|
||||
const sqlEditorStore = useSqlStore()
|
||||
const previousState = usePrevious(content.savingState)
|
||||
const [showSavedText, setShowSavedText] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let cancel = false
|
||||
|
||||
if (
|
||||
(previousState === 'CREATING' || previousState === 'UPDATING') &&
|
||||
content.savingState === 'IDLE'
|
||||
) {
|
||||
setShowSavedText(true)
|
||||
setTimeout(() => {
|
||||
if (!cancel) setShowSavedText(false)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
return () => (cancel = true)
|
||||
}, [content.savingState])
|
||||
|
||||
const retry = () => {
|
||||
const [item] = content.list((item) => item.id === sqlEditorStore.selectedTabId)
|
||||
|
||||
if (content.savingState === 'CREATING_FAILED' && item) {
|
||||
content.save(toJS(item))
|
||||
}
|
||||
|
||||
if (content.savingState === 'UPDATING_FAILED' && item) {
|
||||
updateSqlSnippet(sqlEditorStore.activeTab.query)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-2 flex items-center gap-2">
|
||||
{(content.savingState === 'CREATING_FAILED' || content.savingState === 'UPDATING_FAILED') && (
|
||||
<Button
|
||||
type="text"
|
||||
size="tiny"
|
||||
shadow={false}
|
||||
icon={<IconRefreshCcw className="text-gray-1100" size="tiny" strokeWidth={2} />}
|
||||
onClick={retry}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
{(content.savingState === 'CREATING' || content.savingState === 'UPDATING') && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">Saving changes...</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{(content.savingState === 'CREATING_FAILED' || content.savingState === 'UPDATING_FAILED') && (
|
||||
<IconAlertCircle className="text-red-900" size={14} strokeWidth={2} />
|
||||
)}
|
||||
{showSavedText && (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<IconCheck className="text-brand-800" size={14} strokeWidth={3} />
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border ',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">All changes saved</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
<span className="text-scale-1000 text-sm">
|
||||
{content.savingState === 'CREATING_FAILED' && 'Failed to create'}
|
||||
{content.savingState === 'UPDATING_FAILED' && 'Failed to save'}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const UtilityActions = observer(({ updateSqlSnippet }) => {
|
||||
const sqlEditorStore = useSqlStore()
|
||||
|
||||
useKeyboardShortcuts(
|
||||
@@ -291,6 +382,7 @@ const UtilityActions = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SavingIndicator updateSqlSnippet={updateSqlSnippet} />
|
||||
{IS_PLATFORM && <FavoriteButton />}
|
||||
<SizeToggleButton />
|
||||
<Button
|
||||
@@ -352,14 +444,10 @@ const SizeToggleButton = observer(() => {
|
||||
})
|
||||
|
||||
const FavoriteButton = observer(() => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
const { ui } = useStore()
|
||||
const { ui, content: contentStore } = useStore()
|
||||
const { profile: user } = ui
|
||||
|
||||
const sqlEditorStore = useSqlStore()
|
||||
const contentStore = useProjectContentStore(ref)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
@@ -397,8 +485,9 @@ const FavoriteButton = observer(() => {
|
||||
/*
|
||||
* reload sql data in store and re-select tab
|
||||
*/
|
||||
await sqlEditorStore.loadRemotePersistentData(contentStore, user.id)
|
||||
sqlEditorStore.loadTabs(sqlEditorStore.tabsFromContentStore(contentStore, user?.id), false)
|
||||
sqlEditorStore.selectTab(id)
|
||||
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
ui.setNotification({
|
||||
@@ -434,7 +523,7 @@ const FavoriteButton = observer(() => {
|
||||
/*
|
||||
* reload sql data in store and re-select tab
|
||||
*/
|
||||
await sqlEditorStore.loadRemotePersistentData(contentStore, user.id)
|
||||
sqlEditorStore.loadTabs(sqlEditorStore.tabsFromContentStore(contentStore, user?.id), false)
|
||||
sqlEditorStore.selectTab(id)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
@@ -479,14 +568,14 @@ const UtilityTabResults = observer(() => {
|
||||
if (sqlEditorStore.activeTab.isExecuting) {
|
||||
return (
|
||||
<div className="bg-table-header-light dark:bg-table-header-dark">
|
||||
<p className="px-6 py-4 m-0 border-0 font-mono text-sm">Running...</p>
|
||||
<p className="m-0 border-0 px-6 py-4 font-mono text-sm">Running...</p>
|
||||
</div>
|
||||
)
|
||||
} else if (sqlEditorStore.activeTab.errorResult) {
|
||||
return (
|
||||
<div className="bg-table-header-light dark:bg-table-header-dark">
|
||||
<Typography.Text>
|
||||
<p className="px-6 py-4 m-0 border-0 font-mono">{sqlEditorStore.activeTab.errorResult}</p>
|
||||
<p className="m-0 border-0 px-6 py-4 font-mono">{sqlEditorStore.activeTab.errorResult}</p>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
@@ -494,7 +583,7 @@ const UtilityTabResults = observer(() => {
|
||||
return (
|
||||
<div className="bg-table-header-light dark:bg-table-header-dark">
|
||||
<Typography.Text type="secondary">
|
||||
<p className="px-6 py-4 m-0 border-0 ">
|
||||
<p className="m-0 border-0 px-6 py-4 ">
|
||||
Click <Typography.Text code>RUN</Typography.Text> to execute your query.
|
||||
</p>
|
||||
</Typography.Text>
|
||||
@@ -530,7 +619,7 @@ const Results = ({ results }) => {
|
||||
return (
|
||||
<div className="bg-table-header-light dark:bg-table-header-dark">
|
||||
<Typography.Text type="danger">
|
||||
<p className="px-6 py-4 m-0 font-mono border-0"> {`ERROR: ${results.error}`}</p>
|
||||
<p className="m-0 border-0 px-6 py-4 font-mono"> {`ERROR: ${results.error}`}</p>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
@@ -538,7 +627,7 @@ const Results = ({ results }) => {
|
||||
if (!results.length) {
|
||||
return (
|
||||
<div className="bg-table-header-light dark:bg-table-header-dark">
|
||||
<p className="px-6 py-4 m-0 font-mono border-0 text-sm">Success. No rows returned</p>
|
||||
<p className="m-0 border-0 px-6 py-4 font-mono text-sm">Success. No rows returned</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -547,7 +636,7 @@ const Results = ({ results }) => {
|
||||
return <span className="font-mono text-xs">{JSON.stringify(row[column])}</span>
|
||||
}
|
||||
const columnRender = (name) => {
|
||||
return <div className="flex items-center justify-center font-mono h-full">{name}</div>
|
||||
return <div className="flex h-full items-center justify-center font-mono">{name}</div>
|
||||
}
|
||||
const columns = Object.keys(results[0]).map((key) => ({
|
||||
key,
|
||||
|
||||
@@ -1,72 +1,41 @@
|
||||
import React, { useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { IconChevronRight, IconLoader, Typography } from '@supabase/ui'
|
||||
import { IconChevronRight, Typography } from '@supabase/ui'
|
||||
|
||||
import { useSqlStore } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { useStore } from 'hooks'
|
||||
import { useOptimisticSqlSnippetCreate } from 'hooks'
|
||||
import { partition } from 'lodash'
|
||||
|
||||
import { createSqlSnippet } from './SqlEditor.utils'
|
||||
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants'
|
||||
import CardButton from 'components/ui/CardButton'
|
||||
|
||||
const TabWelcome = observer(() => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' })
|
||||
|
||||
const { ui } = useStore()
|
||||
const { profile: user } = ui
|
||||
|
||||
const sqlEditorStore = useSqlStore()
|
||||
const contentStore = useProjectContentStore(ref)
|
||||
|
||||
const [SQL, QuickStart] = partition(SQL_TEMPLATES, { type: 'template' })
|
||||
|
||||
async function handleNewQuery(sql, title) {
|
||||
try {
|
||||
//do what you need here
|
||||
// create new sql snippet
|
||||
// this also reloads the project_content store
|
||||
const snippet = await createSqlSnippet({ router, sql, name: title })
|
||||
|
||||
// reload the local sqlEditorStore
|
||||
await sqlEditorStore.loadRemotePersistentData(contentStore, user?.id)
|
||||
|
||||
// select tab with new snippet
|
||||
sqlEditorStore.selectTab(snippet.id)
|
||||
} catch (error) {
|
||||
ui.setNotification({
|
||||
error,
|
||||
category: 'error',
|
||||
message: `Failed to create new query: ${error.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleNewQuery = useOptimisticSqlSnippetCreate()
|
||||
|
||||
return (
|
||||
<div className="block p-6 h-full overflow-y-auto space-y-8">
|
||||
<div className="block h-full space-y-8 overflow-y-auto p-6">
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<h1 className="text-scale-1200 text-xl mb-3">Scripts</h1>
|
||||
<p className="text-sm text-scale-1100">Quick scripts to run on your database.</p>
|
||||
<p className="text-sm text-scale-1100">
|
||||
<h1 className="text-scale-1200 mb-3 text-xl">Scripts</h1>
|
||||
<p className="text-scale-1100 text-sm">Quick scripts to run on your database.</p>
|
||||
<p className="text-scale-1100 text-sm">
|
||||
Click on any script to fill the query box, modify the script, then click
|
||||
<span className="text-code">Run</span>.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
|
||||
{SQL.map((x) => (
|
||||
<div className="grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{sql.map((x) => (
|
||||
<SqlCard
|
||||
key={x.title}
|
||||
title={x.title}
|
||||
description={x.description}
|
||||
sql={x.sql}
|
||||
onClick={(sql, title) => {
|
||||
handleNewQuery(sql, title)
|
||||
handleNewQuery({ sql, name: title })
|
||||
Telemetry.sendEvent('scripts', 'script_clicked', x.title)
|
||||
}}
|
||||
/>
|
||||
@@ -75,18 +44,18 @@ const TabWelcome = observer(() => {
|
||||
</div>
|
||||
<div className="mb-8">
|
||||
<div className="mb-4">
|
||||
<h1 className="text-scale-1200 text-xl mb-3">Quick start</h1>
|
||||
<p className="text-sm text-scale-1100">
|
||||
<h1 className="text-scale-1200 mb-3 text-xl">Quick start</h1>
|
||||
<p className="text-scale-1100 text-sm">
|
||||
While we're in beta, we want to offer a quick way to explore Supabase. While we build
|
||||
importers, check out these simple starters.
|
||||
</p>
|
||||
<p className="text-sm text-scale-1100">
|
||||
<p className="text-scale-1100 text-sm">
|
||||
Click on any script to fill the query box, modify the script, then click
|
||||
<span className="text-code">Run</span>.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
|
||||
{QuickStart.map((x) => (
|
||||
<div className="grid gap-4 sm:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
{quickStart.map((x) => (
|
||||
<SqlCard
|
||||
key={x.title}
|
||||
title={x.title}
|
||||
@@ -117,7 +86,7 @@ const SqlCard = ({ title, description, sql, onClick }) => {
|
||||
<CardButton
|
||||
onClick={() => handleOnClick()}
|
||||
title={title}
|
||||
footer={<span className="text-sm text-scale-1100">{description}</span>}
|
||||
footer={<span className="text-scale-1100 text-sm">{description}</span>}
|
||||
>
|
||||
{/* <a
|
||||
className="rounded bg-panel-header-light dark:bg-panel-header-dark transition-colors
|
||||
@@ -157,13 +126,13 @@ const FavoriteCard = observer(({ favorite }) => {
|
||||
|
||||
return (
|
||||
<a
|
||||
className="rounded bg-panel-header-light dark:bg-panel-header-dark transition-colors
|
||||
border border-panel-border-light dark:border-panel-border-dark
|
||||
hover:border-panel-border-hover-light dark:hover:border-panel-border-hover-dark
|
||||
cursor-pointer"
|
||||
className="bg-panel-header-light dark:bg-panel-header-dark border-panel-border-light dark:border-panel-border-dark
|
||||
hover:border-panel-border-hover-light dark:hover:border-panel-border-hover-dark cursor-pointer
|
||||
rounded border
|
||||
transition-colors"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="p-6 py-3 border-b dark:border-dark flex items-center justify-between">
|
||||
<div className="dark:border-dark flex items-center justify-between border-b p-6 py-3">
|
||||
<Typography.Title level={6} className="m-0">
|
||||
{name}
|
||||
</Typography.Title>
|
||||
@@ -171,7 +140,7 @@ const FavoriteCard = observer(({ favorite }) => {
|
||||
<IconChevronRight />
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<p className="p-6 py-4 capitalize-first">
|
||||
<p className="capitalize-first p-6 py-4">
|
||||
<Typography.Text type="secondary">{desc}</Typography.Text>
|
||||
</p>
|
||||
</a>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Modal, Button, Space } from '@supabase/ui'
|
||||
import { MouseEventHandler } from 'react'
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
|
||||
interface Props {
|
||||
@@ -34,7 +35,9 @@ const ConfirmationModal: FC<Props> = ({
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const onConfirm = () => {
|
||||
const onConfirm: MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setLoading(true)
|
||||
onSelectConfirm()
|
||||
}
|
||||
@@ -48,7 +51,7 @@ const ConfirmationModal: FC<Props> = ({
|
||||
size={size}
|
||||
onCancel={onSelectCancel}
|
||||
customFooter={
|
||||
<div className="flex items-center w-full space-x-3">
|
||||
<div className="flex w-full items-center space-x-3">
|
||||
<Button block type="secondary" onClick={onSelectCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './misc/useStore'
|
||||
export * from './misc/withAuth'
|
||||
export * from './misc/useOptimisticSqlSnippetCreate'
|
||||
export * from './misc/useNotifications'
|
||||
export * from './deprecated'
|
||||
export * from './queries'
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { createSqlSnippetSkeleton } from 'components/to-be-cleaned/SqlEditor/SqlEditor.utils'
|
||||
import { useSqlStore } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import { useCallback } from 'react'
|
||||
import { useStore } from './useStore'
|
||||
|
||||
export function useOptimisticSqlSnippetCreate() {
|
||||
const { ui, content: contentStore } = useStore()
|
||||
const { profile: user } = ui
|
||||
|
||||
const sqlEditorStore: any = useSqlStore()
|
||||
|
||||
return useCallback(
|
||||
async (args?: Parameters<typeof createSqlSnippetSkeleton>[0]) => {
|
||||
// get currently selected tab id in case we need to roll back to it
|
||||
const previouslySelectedTabId = sqlEditorStore.selectedTabId
|
||||
|
||||
const snippet = createSqlSnippetSkeleton({ owner_id: user?.id, ...args })
|
||||
|
||||
// save the snippet in memory in the content store with a client-generated id
|
||||
const { data } = contentStore.createOptimistically(snippet)
|
||||
|
||||
// update the tabs in the sql editor storage to match the content store
|
||||
const tabs = sqlEditorStore.tabsFromContentStore(contentStore, user?.id)
|
||||
sqlEditorStore.loadTabs(tabs, false)
|
||||
|
||||
// select tab with new snippet
|
||||
sqlEditorStore.selectTab(data.id)
|
||||
|
||||
// save the new query to the server in the background
|
||||
const { error } = await contentStore.save(data)
|
||||
if (error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to create new query: ${error.message}`,
|
||||
})
|
||||
}
|
||||
},
|
||||
[user, sqlEditorStore, contentStore]
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { v4 as _uuidV4 } from 'uuid'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { PASSWORD_STRENGTH, DEFAULT_MINIMUM_PASSWORD_STRENGTH, API_URL } from 'lib/constants'
|
||||
|
||||
@@ -32,14 +33,8 @@ export const prettifyJSON = (minifiedJSON: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/2117523
|
||||
// a "good enough" unique ID that typescript is happy with, and doesn't have external dependencies
|
||||
export const uuidv4 = () => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
return _uuidV4()
|
||||
}
|
||||
|
||||
export const timeout = (ms: number) => {
|
||||
|
||||
@@ -5,17 +5,23 @@ import { IS_PLATFORM } from 'lib/constants'
|
||||
import Tab from './Tab'
|
||||
import QueryTab from './QueryTab'
|
||||
import Favorite from './Favorite'
|
||||
// import { useStore } from 'hooks'
|
||||
import { SchemasQuery, TableColumnsQuery, AllFunctionsQuery } from './queries'
|
||||
import { isUndefined } from 'lodash'
|
||||
|
||||
let store = null
|
||||
let projectRef = null
|
||||
let metaProjectRef = null
|
||||
export function useSqlEditorStore(ref, meta) {
|
||||
if (store === null || ref !== projectRef) {
|
||||
if ((ref && ref !== projectRef) || (meta?.projectRef && meta.projectRef !== metaProjectRef)) {
|
||||
projectRef = ref
|
||||
metaProjectRef = meta.projectRef
|
||||
store = new SqlEditorStore(ref, meta)
|
||||
}
|
||||
|
||||
if (!ref || !meta) {
|
||||
store = null
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
@@ -70,6 +76,10 @@ class SqlEditorStore {
|
||||
}
|
||||
|
||||
get activeTab() {
|
||||
if (this.tabs.length === 1) {
|
||||
this.selectedTabId = this.tabs[0].id
|
||||
}
|
||||
|
||||
const found = this.tabs.find((x) => x.id === this.selectedTabId)
|
||||
return found
|
||||
}
|
||||
@@ -112,22 +122,16 @@ class SqlEditorStore {
|
||||
else return tab.isExecuting
|
||||
}
|
||||
|
||||
/*
|
||||
* ! Temporary solution !
|
||||
*
|
||||
* New action for retriving data from main db user_content
|
||||
* You need to pass the contentStore into this
|
||||
*/
|
||||
async loadRemotePersistentData(contentStore, user_id) {
|
||||
await contentStore.load()
|
||||
const sqlSnippets = contentStore.sqlSnippets((x) => x.owner_id === user_id)
|
||||
const snippets = toJS(sqlSnippets)
|
||||
tabsFromContentStore(contentStore, user_id) {
|
||||
const snippets = contentStore
|
||||
.sqlSnippets((x) => x.owner_id === user_id)
|
||||
.map((snippet) => toJS(snippet))
|
||||
|
||||
// add the welcome tab
|
||||
let tabs = IS_PLATFORM ? [new Tab('Welcome', TAB_TYPES.WELCOME)] : []
|
||||
|
||||
// add the tabs to array, but with structure the localStore expects
|
||||
snippets.map((snippet) => {
|
||||
snippets.forEach((snippet) => {
|
||||
const data = {
|
||||
desc: snippet.description,
|
||||
id: snippet.id,
|
||||
@@ -136,9 +140,24 @@ class SqlEditorStore {
|
||||
type: 'SQL_QUERY',
|
||||
favorite: snippet.content.favorite,
|
||||
}
|
||||
|
||||
tabs.push(data)
|
||||
})
|
||||
|
||||
return tabs
|
||||
}
|
||||
|
||||
/*
|
||||
* ! Temporary solution !
|
||||
*
|
||||
* New action for retriving data from main db user_content
|
||||
* You need to pass the contentStore into this
|
||||
*/
|
||||
async loadRemotePersistentData(contentStore, user_id) {
|
||||
await contentStore.load()
|
||||
|
||||
const tabs = this.tabsFromContentStore(contentStore, user_id)
|
||||
|
||||
/*
|
||||
* Reshape snippet content to fit the SqlEditorStore shape
|
||||
*/
|
||||
@@ -197,7 +216,7 @@ class SqlEditorStore {
|
||||
this.selectedTabId = this.tabs.length ? this.tabs[0].id : undefined
|
||||
}
|
||||
|
||||
loadTabs(values) {
|
||||
loadTabs(values, autoSelectTab = true) {
|
||||
const tabs = values.map((x) => {
|
||||
switch (x.type) {
|
||||
case TAB_TYPES.WELCOME:
|
||||
@@ -213,7 +232,10 @@ class SqlEditorStore {
|
||||
})
|
||||
|
||||
this.tabs = tabs.filter((x) => x !== undefined)
|
||||
this.selectedTabId = this.tabs.length ? this.tabs[0].id : undefined
|
||||
|
||||
if (autoSelectTab) {
|
||||
this.selectedTabId = this.tabs.length ? this.tabs[0].id : undefined
|
||||
}
|
||||
}
|
||||
|
||||
loadFavorites(values) {
|
||||
@@ -279,10 +301,15 @@ class SqlEditorStore {
|
||||
}
|
||||
|
||||
closeTab(id) {
|
||||
this.tabs = this.tabs.filter((x) => x.id !== id)
|
||||
const tabs = this.tabs.filter((x) => x.id !== id)
|
||||
// if user close selectedTab, select the last tab if available
|
||||
if (this.tabs.length && this.selectedTabId === id)
|
||||
this.selectedTabId = this.tabs[this.tabs.length - 1].id
|
||||
if (tabs.length && this.selectedTabId === id) {
|
||||
const nextId = tabs[tabs.length - 1].id
|
||||
|
||||
if (nextId) {
|
||||
this.selectedTabId = nextId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createQueryTab(query, name) {
|
||||
@@ -331,10 +358,10 @@ class SqlEditorStore {
|
||||
}
|
||||
|
||||
renameQuery(id, model) {
|
||||
const found = this.tabs.find((x) => x.id == id)
|
||||
const found = this.tabs.find((x) => x.id === id)
|
||||
found?.rename(model)
|
||||
|
||||
const favorite = this.favorites.find((x) => x.key == id)
|
||||
const favorite = this.favorites.find((x) => x.key === id)
|
||||
favorite?.rename(model)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@stripe/stripe-js": "^1.25.0",
|
||||
"@supabase/postgres-meta": "^0.26.1",
|
||||
"@supabase/react-data-grid": "^7.1.0-beta.7",
|
||||
"@supabase/shared-types": "^0.1.21",
|
||||
"@supabase/shared-types": "^0.1.24",
|
||||
"@supabase/supabase-js": "^1.35.3",
|
||||
"@supabase/ui": "0.37.0-alpha.58",
|
||||
"ajv": "^8.6.3",
|
||||
@@ -8338,9 +8338,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@supabase/shared-types": {
|
||||
"version": "0.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/shared-types/-/shared-types-0.1.21.tgz",
|
||||
"integrity": "sha512-iF6XMy2cYPm7p3hximwuDmQOFZuVHVKr/uKcrtQlFrQIYjmMMrF/7Qm20muq2+oH8WNxcj29AZJ6oBx6mYzEqQ=="
|
||||
"version": "0.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/shared-types/-/shared-types-0.1.24.tgz",
|
||||
"integrity": "sha512-zvGWWLLh5ZAeiSR4VfJGBY9KbRb1Jh8//gJVmZwiaRv0i6F4i9qTsFOmWjpFgLY5+grAqwSfyIfB+jG8rxcidQ=="
|
||||
},
|
||||
"node_modules/@supabase/storage-js": {
|
||||
"version": "1.7.0",
|
||||
@@ -35112,9 +35112,9 @@
|
||||
}
|
||||
},
|
||||
"@supabase/shared-types": {
|
||||
"version": "0.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/shared-types/-/shared-types-0.1.21.tgz",
|
||||
"integrity": "sha512-iF6XMy2cYPm7p3hximwuDmQOFZuVHVKr/uKcrtQlFrQIYjmMMrF/7Qm20muq2+oH8WNxcj29AZJ6oBx6mYzEqQ=="
|
||||
"version": "0.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@supabase/shared-types/-/shared-types-0.1.24.tgz",
|
||||
"integrity": "sha512-zvGWWLLh5ZAeiSR4VfJGBY9KbRb1Jh8//gJVmZwiaRv0i6F4i9qTsFOmWjpFgLY5+grAqwSfyIfB+jG8rxcidQ=="
|
||||
},
|
||||
"@supabase/storage-js": {
|
||||
"version": "1.7.0",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@stripe/stripe-js": "^1.25.0",
|
||||
"@supabase/postgres-meta": "^0.26.1",
|
||||
"@supabase/react-data-grid": "^7.1.0-beta.7",
|
||||
"@supabase/shared-types": "^0.1.21",
|
||||
"@supabase/shared-types": "^0.1.24",
|
||||
"@supabase/supabase-js": "^1.35.3",
|
||||
"@supabase/ui": "0.37.0-alpha.58",
|
||||
"ajv": "^8.6.3",
|
||||
@@ -74,6 +74,7 @@
|
||||
"swr": "^1.2.1",
|
||||
"uniforms-bootstrap4": "^3.6.2",
|
||||
"uniforms-bridge-json-schema": "^3.6.2",
|
||||
"uuid": "^8.3.2",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -104,6 +105,7 @@
|
||||
"@types/react-window-infinite-loader": "^1.0.5",
|
||||
"@types/semver": "^7.3.9",
|
||||
"@types/sqlstring": "^2.3.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/zxcvbn": "^4.4.1",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"babel-loader": "^8.2.3",
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, Select } from '@supabase/ui'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { get, post } from 'lib/common/fetch'
|
||||
|
||||
const Associate = observer(() => {
|
||||
const { ui, app } = useStore()
|
||||
const sortedOrganizations = app.organizations.list()
|
||||
|
||||
const [awsMarketplace, setAwsMarketplace] = useState<any>()
|
||||
const [error, setError] = useState<any>('')
|
||||
const [loading, setLoading] = useState<any>(true)
|
||||
const [organizationSlug, setOrganizationSlug] = useState<any>('')
|
||||
const [saving, setSaving] = useState<any>(false)
|
||||
const router = useRouter()
|
||||
|
||||
const handleChange = (e: any) => {
|
||||
setOrganizationSlug(e.target.value)
|
||||
}
|
||||
|
||||
const onSave = async () => {
|
||||
if (organizationSlug === '') {
|
||||
setError('Please select an organization')
|
||||
return
|
||||
}
|
||||
setSaving(true)
|
||||
const { error } = await post(
|
||||
`${API_URL}/organizations/${organizationSlug}/associate`,
|
||||
awsMarketplace
|
||||
)
|
||||
if (error) {
|
||||
setError(error.message)
|
||||
}
|
||||
setSaving(false)
|
||||
if (!error) {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const getAwsMarketplaceInfo = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const xAmznMarketplaceToken = router.query['x-amzn-marketplace-token']
|
||||
if (typeof xAmznMarketplaceToken !== 'string') {
|
||||
throw new Error('Missing or multiple x-amzn-marketplace-token')
|
||||
}
|
||||
const response = await get(
|
||||
`${API_URL}/organizations/aws-marketplace?x-amzn-marketplace-token=${encodeURIComponent(
|
||||
xAmznMarketplaceToken
|
||||
)}`
|
||||
)
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
if (response.slug) {
|
||||
setOrganizationSlug(response.slug)
|
||||
}
|
||||
setAwsMarketplace(response.awsMarketplace)
|
||||
setLoading(false)
|
||||
if (sortedOrganizations.length === 1) {
|
||||
onSave()
|
||||
}
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to get AWS Marketplace Information: ${error.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
getAwsMarketplaceInfo()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
{!loading && (
|
||||
<div>
|
||||
<Select
|
||||
className="my-4"
|
||||
error={error}
|
||||
label="Select an organization to associate AWS Marketplace subscription with:"
|
||||
onChange={handleChange}
|
||||
value={organizationSlug}
|
||||
>
|
||||
<Select.Option key="" value="">
|
||||
{' '}
|
||||
</Select.Option>
|
||||
{sortedOrganizations.map((organization: any) => (
|
||||
<Select.Option key={organization.slug} value={organization.slug}>
|
||||
{organization.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Button type="primary" loading={saving} onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default Associate
|
||||
@@ -105,9 +105,7 @@ Home.getLayout = (page) => <IndexLayout>{page}</IndexLayout>
|
||||
export default observer(Home)
|
||||
|
||||
// detect for redirect from 3rd party service like vercel, aws...
|
||||
function isRedirectFromThirdPartyService(router: NextRouter) {
|
||||
return router.query.next !== undefined || router.query['x-amzn-marketplace-token'] !== undefined
|
||||
}
|
||||
const isRedirectFromThirdPartyService = (router: NextRouter) => router.query.next !== undefined
|
||||
|
||||
const UnauthorizedLanding = () => {
|
||||
const router = useRouter()
|
||||
@@ -148,8 +146,6 @@ const IndexLayout = withAuth(
|
||||
router.push(`/vercel/integrate?${params.toString()}`)
|
||||
} else if (router.query?.next?.includes('new-project')) {
|
||||
router.push('/new/project')
|
||||
} else if (router.query['x-amzn-marketplace-token'] != undefined) {
|
||||
router.push(`/account/associate?${params.toString()}`)
|
||||
} else if (
|
||||
typeof router.query?.next === 'string' &&
|
||||
router.query?.next?.startsWith('project/_/')
|
||||
|
||||
@@ -1,58 +1,16 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useMonaco } from '@monaco-editor/react'
|
||||
import {
|
||||
useSqlEditorStore,
|
||||
SqlEditorContext,
|
||||
useSqlStore,
|
||||
TAB_TYPES,
|
||||
} from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import { useSqlStore, TAB_TYPES } from 'localStores/sqlEditor/SqlEditorStore'
|
||||
import TabWelcome from 'components/to-be-cleaned/SqlEditor/TabWelcome'
|
||||
import TabSqlQuery from 'components/to-be-cleaned/SqlEditor/TabSqlQuery'
|
||||
import getPgsqlCompletionProvider from 'components/to-be-cleaned/SqlEditor/PgsqlCompletionProvider'
|
||||
import getPgsqlSignatureHelpProvider from 'components/to-be-cleaned/SqlEditor/PgsqlSignatureHelpProvider'
|
||||
import { useProjectContentStore } from 'stores/projectContentStore'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
import { useStore } from 'hooks'
|
||||
import { SQLEditorLayout } from 'components/layouts'
|
||||
import BaseLayout from 'components/layouts'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
|
||||
const PageConfig = () => {
|
||||
const { meta, ui } = useStore()
|
||||
const { profile: user } = ui
|
||||
|
||||
useEffect(() => {
|
||||
if (ui.selectedProject !== undefined) {
|
||||
loadPersistantData()
|
||||
}
|
||||
}, [ui.selectedProject])
|
||||
|
||||
/*
|
||||
* Load persited data
|
||||
*/
|
||||
async function loadPersistantData() {
|
||||
if (sqlEditorStore === undefined) return
|
||||
await sqlEditorStore.loadRemotePersistentData(contentStore, (user as any)?.id)
|
||||
}
|
||||
|
||||
if (ui.selectedProject === undefined) {
|
||||
return <BaseLayout children={undefined} />
|
||||
}
|
||||
|
||||
const contentStore: any = useProjectContentStore(ui.selectedProject.ref)
|
||||
const sqlEditorStore: any = useSqlEditorStore(ui.selectedProject.ref, meta)
|
||||
|
||||
return (
|
||||
<SqlEditorContext.Provider value={sqlEditorStore}>
|
||||
<SQLEditorLayout title="SQL">
|
||||
<SqlEditor />
|
||||
</SQLEditorLayout>
|
||||
</SqlEditorContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(observer(PageConfig))
|
||||
|
||||
const SqlEditor = observer(() => {
|
||||
const SqlEditor: NextPageWithLayout = () => {
|
||||
const { meta, ui } = useStore()
|
||||
const { isDarkTheme } = ui
|
||||
const sqlEditorStore: any = useSqlStore()
|
||||
@@ -142,4 +100,8 @@ const SqlEditor = observer(() => {
|
||||
}
|
||||
|
||||
return <>{renderTabContent()}</>
|
||||
})
|
||||
}
|
||||
|
||||
SqlEditor.getLayout = (page) => <SQLEditorLayout title="SQL">{page}</SQLEditorLayout>
|
||||
|
||||
export default observer(SqlEditor)
|
||||
@@ -1,6 +1,7 @@
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
import { keyBy } from 'lodash'
|
||||
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import { get, post, patch, delete_ } from 'lib/common/fetch'
|
||||
import { LogSqlSnippets, UserContent, UserContentMap } from 'types'
|
||||
import { IRootStore } from '../RootStore'
|
||||
@@ -14,15 +15,22 @@ export interface IProjectContentStore {
|
||||
isLoading: boolean
|
||||
isInitialized: boolean
|
||||
isLoaded: boolean
|
||||
savingState: 'IDLE' | 'CREATING' | 'CREATING_FAILED' | 'UPDATING' | 'UPDATING_FAILED'
|
||||
error: any
|
||||
recentLogSqlSnippets: LogSqlSnippets.Content[]
|
||||
|
||||
baseUrl: string
|
||||
projectRef?: string
|
||||
|
||||
load: () => void
|
||||
load: () => Promise<UserContentMap | undefined>
|
||||
loadPersistentData: () => Promise<void>
|
||||
create: (x: UserContent) => { data: UserContent; error: { error: { message: string } } }
|
||||
save: (
|
||||
content: UserContent
|
||||
) => Promise<{ data: UserContent; error: null } | { data: null; error: { message: string } }>
|
||||
create: (
|
||||
content: UserContent
|
||||
) => Promise<{ data: UserContent; error: null } | { data: null; error: { message: string } }>
|
||||
createOptimistically: (content: UserContent) => { data: UserContent; error: null }
|
||||
list: (filter?: any) => any[]
|
||||
reports: (filter?: any) => any[]
|
||||
sqlSnippets: (filter?: any) => any[]
|
||||
@@ -30,6 +38,23 @@ export interface IProjectContentStore {
|
||||
addRecentLogSqlSnippet: (snippet: Partial<LogSqlSnippets.Content>) => void
|
||||
clearRecentLogSqlSnippets: () => void
|
||||
setProjectRef: (ref?: string) => void
|
||||
update: (
|
||||
id: any,
|
||||
updates: any,
|
||||
type: UserContent['type']
|
||||
) => Promise<
|
||||
| {
|
||||
data: UserContent
|
||||
error: null
|
||||
}
|
||||
| {
|
||||
data: null
|
||||
error: unknown
|
||||
}
|
||||
>
|
||||
|
||||
del(id: any): Promise<{ data: boolean; error: unknown }>
|
||||
delOptimistically(id: string): { data: boolean; error: null }
|
||||
}
|
||||
|
||||
export default class ProjectContentStore implements IProjectContentStore {
|
||||
@@ -51,6 +76,7 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
recentLogSqlSnippets: LogSqlSnippets.Content[] = []
|
||||
|
||||
state = this.STATES.INITIAL
|
||||
savingState: 'IDLE' | 'CREATING' | 'CREATING_FAILED' | 'UPDATING' | 'UPDATING_FAILED'
|
||||
error = null
|
||||
|
||||
constructor(rootStore: IRootStore, options: { projectRef: string }) {
|
||||
@@ -61,6 +87,7 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
this.recentLogSqlKey = `${this.localStorageKey}-recent-log-sql`
|
||||
this.loadPersistentData()
|
||||
this.baseUrl = ``
|
||||
this.savingState = 'IDLE'
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
@@ -84,7 +111,7 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
if (response.error) {
|
||||
throw response.error
|
||||
}
|
||||
this.data = keyBy(response, 'id')
|
||||
this.data = keyBy(response.data ?? response, 'id')
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -191,37 +218,84 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
return snippets.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
async create(payload: any) {
|
||||
/**
|
||||
* Creates a new content item, but does not update local store
|
||||
*/
|
||||
async save(payload: UserContent) {
|
||||
try {
|
||||
this.savingState = 'CREATING'
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const created = await post(this.baseUrl, payload, { headers })
|
||||
const created = await post<UserContent>(this.baseUrl, payload, { headers })
|
||||
if (created.error) throw created.error
|
||||
this.data[created['id']] = created
|
||||
return { data: created, error: null }
|
||||
|
||||
this.savingState = 'IDLE'
|
||||
|
||||
return { data: created as UserContent, error: null }
|
||||
} catch (error) {
|
||||
return { data: null, error }
|
||||
this.savingState = 'CREATING_FAILED'
|
||||
|
||||
return { data: null, error: error as { message: string } }
|
||||
}
|
||||
}
|
||||
|
||||
async update(id: any, updates: any, type: UserContent['type']) {
|
||||
/**
|
||||
* Creates a new content item, and updates local store
|
||||
*/
|
||||
async create(payload: UserContent) {
|
||||
const { data, error } = await this.save(payload)
|
||||
|
||||
if (error) {
|
||||
return { data: null, error }
|
||||
}
|
||||
|
||||
if (data && data['id']) {
|
||||
this.data[data['id']] = data
|
||||
}
|
||||
|
||||
return { data: data as UserContent, error: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new content item locally in store without saving to the server
|
||||
*/
|
||||
createOptimistically(payload: UserContent) {
|
||||
const created = { ...payload, id: uuidv4() }
|
||||
|
||||
this.data[created.id] = created
|
||||
|
||||
return { data: created, error: null }
|
||||
}
|
||||
|
||||
async update(id: any, updates: any, type?: UserContent['type']) {
|
||||
try {
|
||||
this.savingState = 'UPDATING'
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
let payload = {
|
||||
...updates,
|
||||
type,
|
||||
id,
|
||||
}
|
||||
if (type) {
|
||||
payload.type = type
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}?id=${id}`
|
||||
const updated = await patch(url, payload, { headers })
|
||||
const updated = await patch<UserContent[]>(url, payload, { headers })
|
||||
if (updated.error) throw updated.error
|
||||
this.data[id] = updated
|
||||
return { data: updated, error: null }
|
||||
|
||||
const localUpdate = { ...this.data[id], ...payload }
|
||||
this.data[id] = localUpdate
|
||||
this.savingState = 'IDLE'
|
||||
|
||||
return { data: localUpdate, error: null }
|
||||
} catch (error) {
|
||||
this.savingState = 'UPDATING_FAILED'
|
||||
|
||||
return { data: null, error }
|
||||
}
|
||||
}
|
||||
@@ -249,6 +323,11 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
}
|
||||
}
|
||||
|
||||
delOptimistically(id: string) {
|
||||
delete this.data[id]
|
||||
return { data: true, error: null }
|
||||
}
|
||||
|
||||
setProjectRef(ref?: string) {
|
||||
if (ref) {
|
||||
this.projectRef = ref
|
||||
|
||||
@@ -61,6 +61,8 @@ export interface IMetaStore {
|
||||
publications: IPublicationStore
|
||||
types: IPostgresMetaInterface<any>
|
||||
|
||||
projectRef?: string
|
||||
|
||||
query: (value: string) => Promise<any | { error: ResponseError }>
|
||||
validateQuery: (value: string) => Promise<any | { error: ResponseError }>
|
||||
formatQuery: (value: string) => Promise<any | { error: ResponseError }>
|
||||
@@ -118,6 +120,7 @@ export default class MetaStore implements IMetaStore {
|
||||
publications: PublicationStore
|
||||
types: TypesStore
|
||||
|
||||
projectRef?: string
|
||||
connectionString?: string
|
||||
baseUrl: string
|
||||
excludedSchemas = [
|
||||
@@ -136,6 +139,7 @@ export default class MetaStore implements IMetaStore {
|
||||
constructor(rootStore: IRootStore, options: { projectRef: string; connectionString: string }) {
|
||||
const { projectRef, connectionString } = options
|
||||
this.rootStore = rootStore
|
||||
this.projectRef = projectRef
|
||||
this.baseUrl = `${API_URL}/pg-meta/${projectRef}`
|
||||
|
||||
const headers: any = {}
|
||||
|
||||
@@ -111,15 +111,6 @@ input.form-control,
|
||||
filter: invert(0.8);
|
||||
}
|
||||
|
||||
select {
|
||||
// [Joshen] Color is hard-coded - I can't figure out how to use the UI library colors
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='16' height='16' fill='transparent' stroke='gray' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='4 11 9 16 14 11'></polyline></svg>");
|
||||
}
|
||||
|
||||
select option {
|
||||
@apply text-bg-primary-dark;
|
||||
}
|
||||
|
||||
input.is-invalid {
|
||||
@apply bg-red-100;
|
||||
@apply border border-red-700;
|
||||
|
||||
@@ -7,10 +7,10 @@ description: 'Get started with Supabase and Plasmic, an open-source framework fo
|
||||
In this guide, we will show you how to build a crowd-sourced Pokemon Pokedex, by connecting **Supabase**, an open source Firebase backend alternative, with **Plasmic**, a visual builder for the web. While many users leverage Plasmic to quickly launch and iterate on landing pages, in this tutorial we’ll show just how powerful Plasmic can be as a general-purpose visual builder for React, which can be used to design and implement fully featured read-write applications.
|
||||
|
||||
You can play with the live demo here:
|
||||
[https://plasmic-supabase-demo-git-master-plasmic.vercel.app/](https://plasmic-supabase-demo-git-master-plasmic.vercel.app/login-page)
|
||||
[https://plasmic-supabase-demo.vercel.app/](https://plasmic-supabase-demo.vercel.app/)
|
||||
|
||||
You can also take a look at the Plasmic project here:
|
||||
[https://studio.plasmic.app/projects/i6YZmFxPJB69NEUgdf79ap](https://studio.plasmic.app/projects/i6YZmFxPJB69NEUgdf79ap)
|
||||
[https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6](https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6)
|
||||
|
||||
> You’ll need to enable 3rd-party cookies in your browser for the project to properly load.
|
||||
|
||||
@@ -79,8 +79,8 @@ If you pre-populated the database in Step 1. you should see the following homepa
|
||||
|
||||
Now let’s try to make some additions! The code base is currently configured to a read-only copy of the Plasmic project. Let’s make an editable copy first.
|
||||
|
||||
Open the default starter Plasmic project here:
|
||||
[https://studio.plasmic.app/projects/i6YZmFxPJB69NEUgdf79ap](https://studio.plasmic.app/projects/i6YZmFxPJB69NEUgdf79ap)
|
||||
Open the default starter Plasmic project here:
|
||||
[https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6](https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6)
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ This project comes with TypeScript configured and has the following structure.
|
||||
- A `.env` file: Contains the `DATABASE_URL` variable, which Prisma will use.
|
||||
- A `script.ts` file: where we will run some queries using Prisma Client.
|
||||
This starter also comes with the following packages installed:
|
||||
- [`@prisma/client`](https://www.npmjs.com/package/prisma): An auto-generated and type-safe query builder that’s _tailored_ to your data.
|
||||
- [`prisma`](https://www.npmjs.com/package/@prisma/client): Prisma’s command-line interface (CLI). It allows you to initialize new project assets, generate Prisma Client, and analyze existing database structures through introspection to automatically create your application models.
|
||||
- [`@prisma/client`](https://www.npmjs.com/package/@prisma/client): An auto-generated and type-safe query builder that’s _tailored_ to your data.
|
||||
- [`prisma`](https://www.npmjs.com/package/prisma): Prisma’s command-line interface (CLI). It allows you to initialize new project assets, generate Prisma Client, and analyze existing database structures through introspection to automatically create your application models.
|
||||
> Note: Prisma works with both JavaScript and TypeScript. However, to get the best possible development experience, using TypeScript is highly recommended.
|
||||
|
||||
### Configuring the project to use PostgreSQL
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"mermaid": "^8.13.8",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-inlinesvg": "^2.0.1"
|
||||
"react-inlinesvg": "^2.0.1",
|
||||
"react-loadable": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.17.34",
|
||||
@@ -8719,16 +8720,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.assignin": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
|
||||
"integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI="
|
||||
},
|
||||
"node_modules/lodash.bind": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
|
||||
"integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU="
|
||||
},
|
||||
"node_modules/lodash.curry": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||
@@ -8739,66 +8730,16 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||
},
|
||||
"node_modules/lodash.filter": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
|
||||
"integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4="
|
||||
},
|
||||
"node_modules/lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||
},
|
||||
"node_modules/lodash.flow": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
|
||||
"integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
|
||||
},
|
||||
"node_modules/lodash.foreach": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
|
||||
},
|
||||
"node_modules/lodash.map": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
|
||||
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM="
|
||||
},
|
||||
"node_modules/lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"node_modules/lodash.pick": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
|
||||
"integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM="
|
||||
},
|
||||
"node_modules/lodash.reduce": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||
"integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs="
|
||||
},
|
||||
"node_modules/lodash.reject": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz",
|
||||
"integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU="
|
||||
},
|
||||
"node_modules/lodash.some": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
|
||||
"integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0="
|
||||
},
|
||||
"node_modules/lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
@@ -10800,6 +10741,17 @@
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"node_modules/react-loadable": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz",
|
||||
"integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-loadable-ssr-addon-v5-slorber": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz",
|
||||
@@ -15859,6 +15811,7 @@
|
||||
},
|
||||
"@kiwicopple/prism-react-renderer": {
|
||||
"version": "git+ssh://git@github.com/kiwicopple/prism-react-renderer.git#4a09100a587bce2d94d7ac8ed3564a61c6e70781",
|
||||
"integrity": "sha512-QluokmJkMoHOwahIBN34hTG5n03kFyJ0REDq4mkf0o05JUC61ikXQsPNXtynASqoZRW7A+bKVNT65kCtUfgZlQ==",
|
||||
"from": "@kiwicopple/prism-react-renderer@https://github.com/kiwicopple/prism-react-renderer.git",
|
||||
"requires": {
|
||||
"prismjs": "^1.22.0"
|
||||
@@ -20417,16 +20370,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.assignin": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
|
||||
"integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI="
|
||||
},
|
||||
"lodash.bind": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
|
||||
"integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU="
|
||||
},
|
||||
"lodash.curry": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
|
||||
@@ -20437,66 +20380,16 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||
},
|
||||
"lodash.filter": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
|
||||
"integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4="
|
||||
},
|
||||
"lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||
},
|
||||
"lodash.flow": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
|
||||
"integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
|
||||
},
|
||||
"lodash.foreach": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||
"integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
|
||||
},
|
||||
"lodash.map": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
|
||||
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM="
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lodash.pick": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
|
||||
"integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM="
|
||||
},
|
||||
"lodash.reduce": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
|
||||
"integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs="
|
||||
},
|
||||
"lodash.reject": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz",
|
||||
"integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU="
|
||||
},
|
||||
"lodash.some": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
|
||||
"integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0="
|
||||
},
|
||||
"lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
@@ -21880,6 +21773,14 @@
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-loadable": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz",
|
||||
"integrity": "sha512-C8Aui0ZpMd4KokxRdVAm2bQtI03k2RMRNzOB+IipV3yxFTSVICv7WoUr5L9ALB5BmKO1iHgZtWM8EvYG83otdg==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.0"
|
||||
}
|
||||
},
|
||||
"react-loadable-ssr-addon-v5-slorber": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz",
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"mermaid": "^8.13.8",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-inlinesvg": "^2.0.1"
|
||||
"react-inlinesvg": "^2.0.1",
|
||||
"react-loadable": "^5.5.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -63359,7 +63359,7 @@
|
||||
"isExported": true
|
||||
},
|
||||
"comment": {
|
||||
"text": "A unique identifier for the bucket you are creating.\n"
|
||||
"text": "A unique identifier for the bucket you are updating.\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "intrinsic",
|
||||
@@ -64901,7 +64901,7 @@
|
||||
"isExported": true
|
||||
},
|
||||
"comment": {
|
||||
"text": "A unique identifier for the bucket you are creating.\n"
|
||||
"text": "A unique identifier for the bucket you are updating.\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "intrinsic",
|
||||
|
||||
@@ -1267,7 +1267,7 @@
|
||||
"isExported": true
|
||||
},
|
||||
"comment": {
|
||||
"text": "A unique identifier for the bucket you are creating.\n"
|
||||
"text": "A unique identifier for the bucket you are updating.\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "intrinsic",
|
||||
@@ -2809,7 +2809,7 @@
|
||||
"isExported": true
|
||||
},
|
||||
"comment": {
|
||||
"text": "A unique identifier for the bucket you are creating.\n"
|
||||
"text": "A unique identifier for the bucket you are updating.\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "intrinsic",
|
||||
|
||||
@@ -1449,7 +1449,7 @@ pages:
|
||||
- name: Upload file using `ArrayBuffer` from base64 file data
|
||||
js: |
|
||||
```js
|
||||
import {decode} from 'base64-arraybuffer'
|
||||
import { decode } from 'base64-arraybuffer'
|
||||
|
||||
const { data, error } = await supabase
|
||||
.storage
|
||||
|
||||
@@ -1285,6 +1285,7 @@ h4.method-list-item-label {
|
||||
h4.method-list-item-label .method-list-item-label-name {
|
||||
font-family: var(--ifm-font-family-monospace);
|
||||
font-size: 0.8rem;
|
||||
margin-right: 4px;
|
||||
padding: var(--ifm-code-padding-vertical) 0;
|
||||
}
|
||||
h4.method-list-item-label .method-list-item-label-badge {
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 273 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 403 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 95 KiB |