No files matched your search
@@ -9,7 +9,12 @@ on:
|
||||
- 'supabase/migrations/**'
|
||||
- 'apps/docs/**'
|
||||
- 'spec/**'
|
||||
workflow_dispatch: {}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
refresh:
|
||||
description: 'Refresh all pages'
|
||||
required: false
|
||||
type: boolean
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
@@ -46,6 +51,12 @@ jobs:
|
||||
- name: Run migrations
|
||||
run: npx supabase db push
|
||||
|
||||
- name: Update embeddings
|
||||
working-directory: ./apps/docs
|
||||
if: ${{ !inputs.refresh }}
|
||||
run: npm run embeddings
|
||||
|
||||
- name: Refresh embeddings
|
||||
working-directory: ./apps/docs
|
||||
run: npm run embeddings
|
||||
if: ${{ inputs.refresh }}
|
||||
run: npm run embeddings:refresh
|
||||
@@ -0,0 +1,497 @@
|
||||
import React from 'react'
|
||||
import { LazyMotion, domAnimation, m } from 'framer-motion'
|
||||
|
||||
const DocsCoverLogo = () => {
|
||||
const pathMotionConfig = {
|
||||
initial: { pathLength: 0 },
|
||||
animate: { pathLength: 1 },
|
||||
transition: {
|
||||
duration: 1,
|
||||
delay: 0.2,
|
||||
ease: [0.5, 0.11, 0.13, 1],
|
||||
},
|
||||
}
|
||||
const logoMotionConfig = {
|
||||
initial: { fillOpacity: 0 },
|
||||
animate: { fillOpacity: 1 },
|
||||
transition: {
|
||||
duration: 1,
|
||||
delay: 0.5,
|
||||
ease: [0.25, 0.25, 0, 1],
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-[60px] md:w-[150px] [&_svg]">
|
||||
<LazyMotion features={domAnimation}>
|
||||
<m.svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 250 228"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M124.951 5.03906V224.602"
|
||||
stroke="url(#paint0_radial_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M125.25 191.383C167.828 191.383 202.344 156.867 202.344 114.289C202.344 71.7115 167.828 37.1953 125.25 37.1953C82.6724 37.1953 48.1562 71.7115 48.1562 114.289C48.1562 156.867 82.6724 191.383 125.25 191.383Z"
|
||||
stroke="var(--colors-scale11)"
|
||||
stroke-opacity="0.1"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M33.7782 58.8377C48.6666 58.8377 60.7361 46.7682 60.7361 31.8798C60.7361 16.9913 48.6666 4.92188 33.7782 4.92188C18.8898 4.92188 6.82031 16.9913 6.82031 31.8798C6.82031 46.7682 18.8898 58.8377 33.7782 58.8377Z"
|
||||
stroke="url(#paint1_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M33.7782 223.664C48.6666 223.664 60.7361 211.594 60.7361 196.706C60.7361 181.817 48.6666 169.748 33.7782 169.748C18.8898 169.748 6.82031 181.817 6.82031 196.706C6.82031 211.594 18.8898 223.664 33.7782 223.664Z"
|
||||
stroke="url(#paint2_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M216.724 58.8377C231.612 58.8377 243.681 46.7682 243.681 31.8798C243.681 16.9913 231.612 4.92188 216.724 4.92188C201.835 4.92188 189.766 16.9913 189.766 31.8798C189.766 46.7682 201.835 58.8377 216.724 58.8377Z"
|
||||
stroke="url(#paint3_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M216.724 223.664C231.612 223.664 243.681 211.594 243.681 196.706C243.681 181.817 231.612 169.748 216.724 169.748C201.835 169.748 189.766 181.817 189.766 196.706C189.766 211.594 201.835 223.664 216.724 223.664Z"
|
||||
stroke="url(#paint4_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M209.29 226.982H40.6907C18.7665 226.982 1 209.51 1 187.949V40.0333C1 18.4722 18.7665 1 40.6907 1H209.29C231.214 1 248.981 18.4722 248.981 40.0333V187.969C248.981 209.51 231.214 226.982 209.29 226.982Z"
|
||||
stroke="url(#paint5_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M216.724 223.903H33.7779C18.719 223.903 6.5 211.684 6.5 196.625V31.9576C6.5 16.8987 18.719 4.67969 33.7779 4.67969H216.724C231.782 4.67969 244.001 16.8987 244.001 31.9576V196.625C244.001 211.704 231.802 223.903 216.724 223.903Z"
|
||||
stroke="url(#paint6_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M60.7578 30.9199V197.027"
|
||||
stroke="url(#paint7_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M125.252 5.01953V223.562"
|
||||
stroke="url(#paint8_radial_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M149.809 4.83984L13.5996 178.866"
|
||||
stroke="url(#paint9_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M239.679 46.457L99.5703 223.643"
|
||||
stroke="url(#paint10_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M189.766 30.9199V197.027"
|
||||
stroke="url(#paint11_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M218.024 58.7344H32.498"
|
||||
stroke="url(#paint12_radial_0_1)"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M6.76172 95.4141H243.743"
|
||||
stroke="url(#paint13_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M6.76172 131.869H243.743"
|
||||
stroke="url(#paint14_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M218.024 169.846H32.498"
|
||||
stroke="var(--colors-scale11)"
|
||||
stroke-opacity="0.1"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M126.559 4.6867L126.559 224.254"
|
||||
stroke="url(#paint15_linear_0_1)"
|
||||
stroke-opacity="0.5"
|
||||
stroke-width="0.7"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
/>
|
||||
<g filter="url(#filter0_d_0_1)">
|
||||
<m.path
|
||||
initial={logoMotionConfig.initial}
|
||||
animate={logoMotionConfig.animate}
|
||||
transition={logoMotionConfig.transition as any}
|
||||
d="M125.751 174.003C125.833 179.464 134.091 181.807 137.49 177.53L188.786 112.985C194.84 105.367 189.41 94.1317 179.674 94.1317H126.964L127.046 52.5389C126.964 47.078 119.027 44.7344 115.628 49.0116L65.5801 113.556C59.5255 121.175 64.9558 132.41 74.6925 132.41H125.161L125.751 174.003Z"
|
||||
fill="url(#paint16_linear_0_1)"
|
||||
fill-opacity="0.79"
|
||||
shape-rendering="crispEdges"
|
||||
/>
|
||||
<m.path
|
||||
initial={pathMotionConfig.initial}
|
||||
animate={pathMotionConfig.animate}
|
||||
transition={pathMotionConfig.transition as any}
|
||||
d="M126.964 94.6797L126.416 94.1306L126.498 52.5425C126.459 50.117 124.68 48.3327 122.422 47.6478C120.162 46.9621 117.586 47.432 116.059 49.3499C116.059 49.3508 116.058 49.3516 116.057 49.3525L66.0131 113.892L66.0091 113.897C60.2405 121.156 65.4135 131.862 74.6925 131.862H125.161L125.709 132.402L126.299 173.995L126.299 173.995M126.964 94.6797L125.751 174.003L126.299 173.995M126.964 94.6797H179.674C188.953 94.6797 194.126 105.386 188.357 112.644L137.061 177.189C135.539 179.104 132.883 179.583 130.531 178.892C128.18 178.202 126.336 176.41 126.299 173.995M126.964 94.6797L126.299 173.995"
|
||||
stroke="url(#paint17_linear_0_1)"
|
||||
stroke-width="1.09591"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linejoin="bevel"
|
||||
shape-rendering="crispEdges"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_0_1"
|
||||
x="58.6515"
|
||||
y="46.8027"
|
||||
width="137.062"
|
||||
height="141.703"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4.38364" />
|
||||
<feGaussianBlur stdDeviation="2.19182" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_1" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_0_1"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_0_1"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(125.451 114.82) rotate(90) scale(109.781 0.5)"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_0_1"
|
||||
x1="38.9975"
|
||||
y1="58.9958"
|
||||
x2="18.999"
|
||||
y2="11.9994"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_0_1"
|
||||
x1="43.9971"
|
||||
y1="168.986"
|
||||
x2="32.998"
|
||||
y2="204.984"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_0_1"
|
||||
x1="221.943"
|
||||
y1="58.9958"
|
||||
x2="231.981"
|
||||
y2="13.9993"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_0_1"
|
||||
x1="203.983"
|
||||
y1="168.986"
|
||||
x2="215.943"
|
||||
y2="204.984"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_0_1"
|
||||
x1="121.402"
|
||||
y1="227.645"
|
||||
x2="121.402"
|
||||
y2="-7.94661"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
<stop offset="0.489583" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_0_1"
|
||||
x1="121.814"
|
||||
y1="224.545"
|
||||
x2="121.814"
|
||||
y2="-3.99932"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
<stop offset="0.489583" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint7_linear_0_1"
|
||||
x1="60.7578"
|
||||
y1="30.3703"
|
||||
x2="60.7578"
|
||||
y2="199.061"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0.1" />
|
||||
<stop offset="0.505208" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint8_radial_0_1"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(125.752 114.291) rotate(90) scale(109.271 0.5)"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint9_linear_0_1"
|
||||
x1="6.99856"
|
||||
y1="0"
|
||||
x2="200"
|
||||
y2="6.31757"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.3" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint10_linear_0_1"
|
||||
x1="200"
|
||||
y1="100"
|
||||
x2="0"
|
||||
y2="200"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.3" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint11_linear_0_1"
|
||||
x1="189.766"
|
||||
y1="30.3703"
|
||||
x2="189.766"
|
||||
y2="199.061"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0.1" />
|
||||
<stop offset="0.505208" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0.1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="paint12_radial_0_1"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(125.261 59.2344) rotate(90) scale(0.5 263.749)"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint13_linear_0_1"
|
||||
x1="6.99998"
|
||||
y1="94.9749"
|
||||
x2="242.982"
|
||||
y2="94.9749"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0.5" />
|
||||
<stop offset="0.505208" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint14_linear_0_1"
|
||||
x1="6.99998"
|
||||
y1="131.43"
|
||||
x2="242.982"
|
||||
y2="131.43"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0.5" />
|
||||
<stop offset="0.505208" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint15_linear_0_1"
|
||||
x1="126.559"
|
||||
y1="224.98"
|
||||
x2="126.559"
|
||||
y2="1.99785"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="var(--colors-scale10)" stop-opacity="0.1" />
|
||||
<stop offset="0.505208" stop-color="var(--colors-scale11)" stop-opacity="0.5" />
|
||||
<stop offset="1" stop-color="var(--colors-scale10)" stop-opacity="0.1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint16_linear_0_1"
|
||||
x1="155.11"
|
||||
y1="46.8027"
|
||||
x2="126.979"
|
||||
y2="206.049"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#212121" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.12" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint17_linear_0_1"
|
||||
x1="191.328"
|
||||
y1="82.7684"
|
||||
x2="103.041"
|
||||
y2="175.889"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#868585" />
|
||||
<stop offset="0.208333" stop-color="#838383" />
|
||||
<stop offset="1" stop-color="#5A5A5A" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</m.svg>
|
||||
</LazyMotion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocsCoverLogo
|
||||
@@ -0,0 +1,129 @@
|
||||
import Link from 'next/link'
|
||||
import { IconBackground, IconPanel, IconPlay } from 'ui'
|
||||
import { useBreakpoint } from 'common'
|
||||
import DocsCoverLogo from './DocsCoverLogo'
|
||||
|
||||
const HomePageCover = (props) => {
|
||||
const isXs = useBreakpoint(639)
|
||||
const iconSize = isXs ? 'sm' : 'lg'
|
||||
|
||||
const GettingStarted = () => (
|
||||
<div
|
||||
className="
|
||||
border border-scale-400 bg-scale-200
|
||||
relative overflow-hidden
|
||||
grid grid-cols-12
|
||||
rounded-lg
|
||||
p-5 md:p-8
|
||||
"
|
||||
>
|
||||
<div className="col-span-full flex flex-col md:flex-row xl:flex-col justify-between gap-1 md:gap-6">
|
||||
<div className="md:max-w-xs xl:max-w-none">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<IconBackground>
|
||||
<IconPlay className="text-brand-1100 dark:text-brand-900 w-4" />
|
||||
</IconBackground>
|
||||
<h3 className="text-2xl m-0">Getting Started</h3>
|
||||
</div>
|
||||
<p className="text-scale-1100 text-sm">
|
||||
Discover how to set up a database to an app making queries in just a few minutes.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap md:grid md:grid-cols-4 gap-2 sm:gap-3">
|
||||
<Link href={`/guides/getting-started/quickstarts/reactjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="ReactJS"
|
||||
icon="/docs/img/icons/react-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="NextJS"
|
||||
icon="/docs/img/icons/nextjs-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/redwoodjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="RedwoodJS"
|
||||
icon="/docs/img/icons/redwoodjs-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="Flutter"
|
||||
icon="/docs/img/icons/flutter-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/sveltekit`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="SvelteKit"
|
||||
icon="/docs/img/icons/svelte-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/solidjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="SolidJS"
|
||||
icon="/docs/img/icons/solidjs-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/vue`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel
|
||||
iconSize={iconSize}
|
||||
hideArrow
|
||||
tooltip="Vue"
|
||||
icon="/docs/img/icons/vuejs-icon"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="w-full bg-scale-100 border-b prose dark:prose-dar max-w-none mb-16 md:mb-12 xl:mb-0">
|
||||
<div className="max-w-7xl px-5 mx-auto py-8 sm:pb-16 sm:pt-12 xl:pt-16 flex flex-col xl:flex-row justify-between gap-12 xl:gap-12">
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-8 items-start sm:items-center w-full max-w-xl">
|
||||
<DocsCoverLogo />
|
||||
<div className="flex flex-col">
|
||||
<h1 className="m-0 mb-3 text-2xl sm:text-3xl">{props.meta?.title}</h1>
|
||||
<p className="m-0 text-scale-1100">
|
||||
Learn how to get up and running with Supabase through tutorials, APIs and platform
|
||||
resources.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full xl:max-w-[365px] -mb-40">
|
||||
<GettingStarted />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomePageCover
|
||||
@@ -1,68 +1,163 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { Button, IconGitHub, IconTwitter, IconYoutube } from '~/../../packages/ui'
|
||||
import footerData from '~/data/footer.json'
|
||||
import { useTheme } from 'common/Providers'
|
||||
import { Button } from '~/../../packages/ui'
|
||||
import { primaryLinks, secondaryLinks } from '~/data/footer'
|
||||
import { LayoutMainContent } from '../../layouts/DefaultLayout'
|
||||
|
||||
const Footer = () => {
|
||||
const { isDarkMode } = useTheme()
|
||||
return (
|
||||
<div>
|
||||
<hr className="border-scale-400 mt-8"></hr>
|
||||
<div className="flex gap-4 items-center mt-6 justify-between">
|
||||
<div className="flex flex-col lg:flex-row gap-3 ">
|
||||
<Link href="https://supabase.com/">
|
||||
<a className="text-xs text-scale-900">© Supabase Inc</a>
|
||||
</Link>
|
||||
<span className="text-xs text-scale-900">—</span>
|
||||
{footerData.map((item) => (
|
||||
<Link href={item.url} key={item.url}>
|
||||
<a className="text-xs text-scale-800 hover:underline">{item.title}</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://youtube.com/c/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IconYoutube size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://twitter.com/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<IconTwitter size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://discord.supabase.com/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="16">
|
||||
<path
|
||||
const Footer = () => (
|
||||
<LayoutMainContent className="pt-0">
|
||||
<div className="mt-16">
|
||||
<ul className="flex flex-col gap-2">
|
||||
{primaryLinks.map(({ url, featherIcon: Icon, icon, text, ctaLabel }) => (
|
||||
<li key={url} className="flex items-center gap-1 text-xs text-scale-900">
|
||||
{icon && (
|
||||
<svg
|
||||
width="16"
|
||||
height="17"
|
||||
viewBox="0 0 16 17"
|
||||
fill="none"
|
||||
fillRule="nonzero"
|
||||
stroke={`${isDarkMode ? '#fff' : '#333'}`}
|
||||
d="M16.238 2.16a14.628 14.628 0 0 0-3.664-1.159.055.055 0 0 0-.059.028c-.158.288-.333.662-.456.957a13.432 13.432 0 0 0-4.115 0 9.76 9.76 0 0 0-.464-.957.057.057 0 0 0-.058-.028 14.587 14.587 0 0 0-3.664 1.16.053.053 0 0 0-.024.02C1.4 5.74.76 9.21 1.074 12.637c.002.017.011.033.024.043a14.813 14.813 0 0 0 4.494 2.318.058.058 0 0 0 .064-.02c.346-.483.654-.992.92-1.527a.059.059 0 0 0-.032-.08c-.49-.19-.956-.421-1.404-.684a.06.06 0 0 1-.006-.097c.094-.072.189-.148.279-.223a.055.055 0 0 1 .058-.008c2.946 1.372 6.135 1.372 9.046 0a.055.055 0 0 1 .06.007 7.2 7.2 0 0 0 .279.224.06.06 0 0 1-.005.097 9.143 9.143 0 0 1-1.405.683.06.06 0 0 0-.03.081c.27.534.578 1.043.918 1.526.014.02.04.029.063.021A14.763 14.763 0 0 0 18.9 12.68a.06.06 0 0 0 .023-.042c.376-3.962-.628-7.404-2.66-10.455a.046.046 0 0 0-.024-.021Zm-9.223 8.39c-.887 0-1.618-.831-1.618-1.852 0-1.02.717-1.851 1.618-1.851.908 0 1.632.838 1.618 1.851 0 1.02-.717 1.851-1.618 1.851Zm5.981 0c-.887 0-1.618-.831-1.618-1.852 0-1.02.717-1.851 1.618-1.851.908 0 1.632.838 1.618 1.851 0 1.02-.71 1.851-1.618 1.851Z"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d={icon}
|
||||
stroke="var(--colors-scale9)"
|
||||
strokeWidth={0.7}
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
{Icon && <Icon width={16} height={16} />}
|
||||
<p>{text}</p>
|
||||
<Link href={url} passHref>
|
||||
<a
|
||||
className="text-brand-900 hover:underline"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{ctaLabel}
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<hr className="border-scale-400 my-6"></hr>
|
||||
<div className="flex gap-4 items-center justify-between">
|
||||
<div className="flex flex-col lg:flex-row gap-3 ">
|
||||
<Link href="https://supabase.com/">
|
||||
<a className="text-xs text-scale-900">© Supabase Inc</a>
|
||||
</Link>
|
||||
<span className="text-xs text-scale-900">—</span>
|
||||
{secondaryLinks.map((item) => (
|
||||
<Link href={item.url} key={item.url}>
|
||||
<a className="text-xs text-scale-800 hover:underline">{item.title}</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://github.com/supabase/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<svg
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.66832 1.55469C4.98649 1.55469 2.00195 4.54402 2.00195 8.23269C2.00195 11.1827 3.91187 13.686 6.56108 14.5687C6.8944 14.63 7.01573 14.424 7.01573 14.2467C7.01573 14.0887 7.0104 13.668 7.00706 13.1114C5.15248 13.5147 4.76116 12.216 4.76116 12.216C4.45851 11.444 4.0212 11.2387 4.0212 11.2387C3.41589 10.8254 4.06719 10.8334 4.06719 10.8334C4.73583 10.88 5.08782 11.5214 5.08782 11.5214C5.68246 12.5414 6.64841 12.2467 7.02773 12.076C7.08906 11.6447 7.26105 11.3507 7.45171 11.184C5.97178 11.0154 4.41518 10.442 4.41518 7.88335C4.41518 7.15469 4.67517 6.55802 5.10115 6.09135C5.03248 5.92269 4.80383 5.24335 5.16648 4.32469C5.16648 4.32469 5.72645 4.14469 6.99973 5.00869C7.54355 4.86036 8.10464 4.78482 8.66832 4.78402C9.23496 4.78669 9.80494 4.86069 10.3376 5.00869C11.6102 4.14469 12.1688 4.32402 12.1688 4.32402C12.5328 5.24335 12.3035 5.92269 12.2355 6.09135C12.6621 6.55802 12.9208 7.15469 12.9208 7.88335C12.9208 10.4487 11.3615 11.0134 9.87694 11.1787C10.1163 11.3847 10.3289 11.792 10.3289 12.4154C10.3289 13.3074 10.3209 14.028 10.3209 14.2467C10.3209 14.4254 10.4409 14.6334 10.7796 14.568C12.107 14.1228 13.261 13.2716 14.0784 12.1347C14.8958 10.9979 15.3353 9.6329 15.3347 8.23269C15.3347 4.54402 12.3495 1.55469 8.66832 1.55469Z"
|
||||
fill="var(--colors-scale9)"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://youtube.com/c/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="17"
|
||||
viewBox="0 0 16 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15.665 4.35595C15.481 3.66308 14.9388 3.11739 14.2505 2.93221C13.0028 2.5957 7.99964 2.5957 7.99964 2.5957C7.99964 2.5957 2.99655 2.5957 1.74883 2.93221C1.06047 3.11742 0.518326 3.66308 0.334321 4.35595C0 5.61181 0 8.23207 0 8.23207C0 8.23207 0 10.8523 0.334321 12.1082C0.518326 12.8011 1.06047 13.324 1.74883 13.5092C2.99655 13.8457 7.99964 13.8457 7.99964 13.8457C7.99964 13.8457 13.0027 13.8457 14.2505 13.5092C14.9388 13.324 15.481 12.8011 15.665 12.1082C15.9993 10.8523 15.9993 8.23207 15.9993 8.23207C15.9993 8.23207 15.9993 5.61181 15.665 4.35595ZM6.36334 10.6111V5.85307L10.545 8.23212L6.36334 10.6111Z"
|
||||
fill="var(--colors-scale9)"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://twitter.com/supabase"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<svg
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.52713 13.7223C10.5582 13.7223 13.3101 9.55365 13.3101 5.93898C13.3101 5.82032 13.3101 5.70232 13.3021 5.58565C13.8375 5.198 14.2996 4.71806 14.6667 4.16832C14.1675 4.38965 13.6378 4.53483 13.0955 4.59898C13.6665 4.25702 14.0939 3.71922 14.2981 3.08565C13.7612 3.40424 13.1739 3.6288 12.5615 3.74965C12.1491 3.31083 11.6035 3.02021 11.0093 2.92279C10.4151 2.82537 9.80528 2.92658 9.27438 3.21076C8.74347 3.49494 8.32106 3.94623 8.07255 4.49476C7.82403 5.04328 7.76328 5.65844 7.89969 6.24498C6.81213 6.19048 5.74819 5.90787 4.77693 5.4155C3.80567 4.92313 2.9488 4.232 2.26194 3.38698C1.91217 3.98912 1.80502 4.70193 1.96231 5.38029C2.1196 6.05866 2.52951 6.65157 3.10857 7.03832C2.67343 7.02572 2.2477 6.90865 1.86729 6.69698V6.73165C1.86755 7.36318 2.08621 7.97519 2.4862 8.46389C2.88618 8.95259 3.44287 9.28791 4.06186 9.41298C3.65909 9.52279 3.2365 9.53876 2.82658 9.45965C3.00135 10.0033 3.34168 10.4787 3.79996 10.8194C4.25824 11.1601 4.81154 11.349 5.38247 11.3597C4.81524 11.8054 4.16572 12.1349 3.47104 12.3293C2.77636 12.5238 2.05017 12.5794 1.33398 12.493C2.585 13.2959 4.04066 13.7217 5.52713 13.7197"
|
||||
fill="var(--colors-scale9)"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
as="a"
|
||||
// @ts-ignore
|
||||
href="https://discord.supabase.com/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<svg
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_1182_99731)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M13.8781 3.13594C12.8583 2.66802 11.7648 2.32328 10.6215 2.12583C10.6006 2.12202 10.5798 2.13154 10.5691 2.15059C10.4285 2.40073 10.2727 2.72706 10.1636 2.98356C8.93387 2.79945 7.71044 2.79945 6.50592 2.98356C6.39681 2.72136 6.23538 2.40073 6.09411 2.15059C6.08339 2.13218 6.06259 2.12265 6.04176 2.12583C4.89905 2.32265 3.80554 2.66739 2.78517 3.13594C2.77633 3.13975 2.76876 3.14611 2.76374 3.15435C0.689563 6.25326 0.12136 9.276 0.400102 12.2613C0.401363 12.2759 0.409561 12.2898 0.420913 12.2987C1.78939 13.3037 3.115 13.9139 4.41599 14.3183C4.43681 14.3247 4.45887 14.317 4.47212 14.2999C4.77987 13.8796 5.0542 13.4364 5.28941 12.9704C5.3033 12.9431 5.29005 12.9107 5.26167 12.9C4.82654 12.7349 4.4122 12.5336 4.01364 12.3051C3.98212 12.2867 3.97959 12.2416 4.00859 12.22C4.09247 12.1571 4.17636 12.0917 4.25645 12.0257C4.27094 12.0136 4.29113 12.0111 4.30816 12.0187C6.92655 13.2142 9.76126 13.2142 12.3488 12.0187C12.3658 12.0105 12.386 12.013 12.4011 12.0251C12.4812 12.0911 12.5651 12.1571 12.6496 12.22C12.6786 12.2416 12.6767 12.2867 12.6452 12.3051C12.2466 12.5381 11.8323 12.7349 11.3965 12.8993C11.3681 12.9101 11.3555 12.9431 11.3694 12.9704C11.6097 13.4358 11.884 13.879 12.1861 14.2993C12.1987 14.317 12.2214 14.3247 12.2422 14.3183C13.5495 13.9139 14.8751 13.3037 16.2436 12.2987C16.2556 12.2898 16.2631 12.2765 16.2644 12.2619C16.598 8.8106 15.7056 5.81265 13.8989 3.15498C13.8944 3.14611 13.8869 3.13975 13.8781 3.13594ZM5.68043 10.4435C4.89211 10.4435 4.24257 9.71978 4.24257 8.83093C4.24257 7.94207 4.87952 7.21831 5.68043 7.21831C6.48763 7.21831 7.13089 7.94843 7.11827 8.83093C7.11827 9.71978 6.48132 10.4435 5.68043 10.4435ZM10.9967 10.4435C10.2084 10.4435 9.55884 9.71978 9.55884 8.83093C9.55884 7.94207 10.1958 7.21831 10.9967 7.21831C11.8039 7.21831 12.4471 7.94843 12.4345 8.83093C12.4345 9.71978 11.8039 10.4435 10.9967 10.4435Z"
|
||||
fill="var(--colors-scale9)"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1182_99731">
|
||||
<rect
|
||||
width="15.9993"
|
||||
height="16"
|
||||
fill="white"
|
||||
transform="translate(0.333984 0.222656)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</LayoutMainContent>
|
||||
)
|
||||
|
||||
export default Footer
|
||||
@@ -14,7 +14,7 @@ const NavigationMenuHome = () => {
|
||||
|
||||
return (
|
||||
<div className="transition-all duration-150 ease-out opacity-100 ml-0 delay-150">
|
||||
<ul className="relative w-full flex flex-col gap-4">
|
||||
<ul className="relative w-full flex flex-col gap-4 pb-5">
|
||||
{HOMEPAGE_MENU_ITEMS.map((section, sectionIndex) => {
|
||||
return (
|
||||
<Fragment key={`section-container-${sectionIndex}-border`}>
|
||||
|
||||
@@ -24,15 +24,15 @@ export function IconMenuHome({ width = 16, height = 16 }: HomeMenuIcon) {
|
||||
export function IconMenuApi({ width = 16, height = 16 }: HomeMenuIcon) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.923 10.927A5.833 5.833 0 0 1 2.14 8m2.877-5.046A5.833 5.833 0 0 1 8 2.14m5.13 3.025c.465.84.73 1.807.73 2.835m-2.883 5.049A5.832 5.832 0 0 1 8 13.859m6.172-10.027a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM5.81 12.148a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm8.362 0a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM5.81 3.832a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"
|
||||
stroke="currentColor"
|
||||
d="M11.7118 15.4651C10.8778 15.8159 9.96154 16.0098 9 16.0098C8.03846 16.0098 7.1222 15.8159 6.28818 15.4651M2.42438 11.4156C2.14983 10.6654 2 9.85506 2 9.00977C2 8.16447 2.14983 7.35417 2.42438 6.60397M6.28818 2.55442C7.1222 2.20364 8.03846 2.00977 9 2.00977C9.96154 2.00977 10.8778 2.20364 11.7118 2.55442M15.4553 6.29795C15.8061 7.13197 16 8.04823 16 9.00977C16 9.97131 15.8061 10.8876 15.4553 11.7216M10.7427 10.861L13.0078 13.1261M4.99476 5.11305L7.25997 7.37826M7.14874 10.7525L4.88369 13.0176M12.8967 5.00452L10.6315 7.26974M6.08663 9.00977C6.08663 7.40076 7.39099 6.0964 9 6.0964C10.609 6.0964 11.9134 7.40076 11.9134 9.00977C11.9134 10.6188 10.609 11.9231 9 11.9231C7.39099 11.9231 6.08663 10.6188 6.08663 9.00977ZM3.10575 3.11551C3.58524 2.63602 4.36265 2.63602 4.84214 3.11551C5.32164 3.59501 5.32164 4.37242 4.84214 4.85191C4.36265 5.3314 3.58524 5.3314 3.10575 4.85191C2.62625 4.37242 2.62625 3.59501 3.10575 3.11551ZM13.1579 13.1676C13.6373 12.6881 14.4148 12.6881 14.8943 13.1676C15.3737 13.6471 15.3737 14.4245 14.8943 14.904C14.4148 15.3835 13.6373 15.3835 13.1579 14.904C12.6784 14.4245 12.6784 13.6471 13.1579 13.1676ZM14.8948 3.11549C15.3743 3.59498 15.3743 4.37239 14.8948 4.85189C14.4153 5.33138 13.6379 5.33138 13.1584 4.85189C12.6789 4.37239 12.6789 3.59498 13.1584 3.11549C13.6379 2.636 14.4153 2.636 14.8948 3.11549ZM4.84214 13.1676C5.32164 13.6471 5.32164 14.4245 4.84214 14.904C4.36265 15.3835 3.58524 15.3835 3.10575 14.904C2.62625 14.4245 2.62625 13.6471 3.10575 13.1676C3.58524 12.6881 4.36265 12.6881 4.84214 13.1676Z"
|
||||
stroke="#A0A0A0"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinejoin="bevel"
|
||||
/>
|
||||
@@ -404,17 +404,17 @@ export function IconMenuKotlin({ width = 16, height = 16 }: HomeMenuIcon) {
|
||||
export function IconMenuStatus({ width = 16, height = 16 }: HomeMenuIcon) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinejoin="bevel"
|
||||
d="M8.9895 1.13281C4.64498 1.13281 1.12305 4.65474 1.12305 8.99927C1.12305 13.3438 4.64498 16.8657 8.9895 16.8657C13.334 16.8657 16.856 13.3438 16.856 8.99927C16.856 7.13428 16.207 5.42087 15.1225 4.07258L8.35267 10.8424L6.39571 8.88546M9.06632 4.19659C6.42145 4.19659 4.27737 6.34068 4.27737 8.98555C4.27737 11.6304 6.42145 13.7745 9.06632 13.7745C11.7112 13.7745 13.8553 11.6304 13.8553 8.98555C13.8553 7.99916 13.5571 7.08243 13.0459 6.32059"
|
||||
stroke="#A0A0A0"
|
||||
strokeMiterlimit="10"
|
||||
d="M8.99 1.133a7.866 7.866 0 1 0 6.133 2.94l-6.77 6.77-1.956-1.958m2.67-4.688a4.789 4.789 0 1 0 3.98 2.124"
|
||||
strokeLinejoin="bevel"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ const NavigationMenuCliList = ({ currentLevel, setLevel, id }) => {
|
||||
}
|
||||
|
||||
const Divider = () => {
|
||||
return <div className="h-px w-full bg-green-500 my-3"></div>
|
||||
return <div className="h-px w-full bg-scale-500 my-3"></div>
|
||||
}
|
||||
|
||||
const MenuSections = [
|
||||
|
||||
@@ -54,7 +54,7 @@ const ContentAccordionLink = React.memo(function ContentAccordionLink(props: any
|
||||
<>
|
||||
{props.subItemIndex === 0 && (
|
||||
<>
|
||||
<div className="h-px w-full bg-green-500 my-3"></div>
|
||||
<div className="h-px w-full bg-scale-500 my-3"></div>
|
||||
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider">
|
||||
{props.parent.name}
|
||||
</span>
|
||||
@@ -147,7 +147,7 @@ const Content = (props) => {
|
||||
const { menu, id } = props
|
||||
|
||||
return (
|
||||
<ul className={['relative w-full flex flex-col gap-0'].join(' ')}>
|
||||
<ul className={['relative w-full flex flex-col gap-0 pb-5'].join(' ')}>
|
||||
<Link href={`${menu.parent ?? '/'}`} passHref>
|
||||
<a
|
||||
className={[
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useMenuActiveRefId } from '~/hooks/useMenuState'
|
||||
import Admonition from '../Admonition'
|
||||
import { ICommonSection } from './Reference.types'
|
||||
|
||||
export interface OldVersionAlertProps {
|
||||
sections: ICommonSection[]
|
||||
}
|
||||
|
||||
const OldVersionAlert = ({ sections }: OldVersionAlertProps) => {
|
||||
const router = useRouter()
|
||||
const activeRefId = useMenuActiveRefId()
|
||||
|
||||
const activeSection = sections.find(({ id }) => id === activeRefId)
|
||||
|
||||
// Remove the version number from URL to get the latest
|
||||
const latestVersionUrl = router.asPath
|
||||
.split('/')
|
||||
.slice(0, -2)
|
||||
.concat(activeSection ? [activeSection.slug] : [])
|
||||
.join('/')
|
||||
|
||||
return (
|
||||
<div className="sticky top-10 z-10 lg:top-14 lg:w-1/2">
|
||||
<Admonition type="caution">
|
||||
You're viewing an older version of this library.
|
||||
<br />
|
||||
<Link href={latestVersionUrl}>
|
||||
<a className="underline decoration-brand-700 underline-offset-4 decoration-1">
|
||||
Switch to the latest
|
||||
</a>
|
||||
</Link>
|
||||
.
|
||||
</Admonition>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OldVersionAlert
|
||||
@@ -8,6 +8,7 @@ import RefFunctionSection from '~/components/reference/RefFunctionSection'
|
||||
import RefSubLayout from '~/layouts/ref/RefSubLayout'
|
||||
import ApiOperationSection from './ApiOperationSection'
|
||||
import CliCommandSection from './CLICommandSection'
|
||||
import OldVersionAlert from './OldVersionAlert'
|
||||
import { IAPISpec, ICommonSection, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types'
|
||||
|
||||
interface RefSectionHandlerProps {
|
||||
@@ -16,12 +17,13 @@ interface RefSectionHandlerProps {
|
||||
typeSpec?: TypeSpec
|
||||
pageProps: { docs: IRefStaticDoc[] }
|
||||
type: 'client-lib' | 'cli' | 'api'
|
||||
isOldVersion?: boolean
|
||||
}
|
||||
|
||||
const RefSectionHandler = (props: RefSectionHandlerProps) => {
|
||||
const router = useRouter()
|
||||
|
||||
const slug = router.query.slug[0]
|
||||
const [slug] = router.query.slug
|
||||
|
||||
// When user lands on a url like http://supabase.com/docs/reference/javascript/sign-up
|
||||
// find the #sign-up element and scroll to that
|
||||
@@ -30,7 +32,7 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => {
|
||||
}, [slug])
|
||||
|
||||
useEffect(() => {
|
||||
function handler(e: PopStateEvent) {
|
||||
function handler() {
|
||||
const [slug] = window.location.pathname.split('/').slice(-1)
|
||||
document.getElementById(slug)?.scrollIntoView()
|
||||
}
|
||||
@@ -69,6 +71,7 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => {
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</Head>
|
||||
{props.isOldVersion && <OldVersionAlert sections={props.sections} />}
|
||||
<RefSubLayout>
|
||||
{props.sections.map((section, i) => {
|
||||
const sectionType = section.type
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
[
|
||||
{ "title": "Contributing", "url": "/handbook/contributing" },
|
||||
{ "title": "Changelog", "url": "https://supabase.com/changelog" },
|
||||
{
|
||||
"title": "Author Styleguide",
|
||||
"url": "https://github.com/supabase/supabase/blob/master/DEVELOPERS.md"
|
||||
},
|
||||
{ "title": "Open Source", "url": "https://supabase.com/open-source" },
|
||||
{ "title": "SupaSquad", "url": "https://supabase.com/supasquad" }
|
||||
]
|
||||
@@ -0,0 +1,32 @@
|
||||
import { IconCheckCircle, IconLifeBuoy } from '~/../../packages/ui'
|
||||
|
||||
export const primaryLinks = [
|
||||
{
|
||||
featherIcon: IconLifeBuoy,
|
||||
text: 'Need some help?',
|
||||
ctaLabel: 'Contact support',
|
||||
url: 'https://app.supabase.com/support/new',
|
||||
},
|
||||
{
|
||||
icon: 'M12.9521 10.5073C12.766 10.3212 12.5289 10.1943 12.2708 10.1427L10.6794 9.82467C9.80719 9.65024 8.90169 9.77152 8.10609 10.1693L7.89409 10.2747C7.0985 10.6725 6.193 10.7938 5.32076 10.6193L4.03343 10.362C3.81823 10.319 3.59574 10.3298 3.38571 10.3934C3.17569 10.457 2.9846 10.5715 2.82943 10.7267M5.33343 2.88867H10.6668L10.0001 3.55534V7.00334C10.0002 7.35693 10.1407 7.69602 10.3908 7.94601L13.7241 11.2793C14.5641 12.1193 13.9688 13.5553 12.7808 13.5553H3.21876C2.03076 13.5553 1.43609 12.1193 2.27609 11.2793L5.60943 7.94601C5.85949 7.69602 6.00002 7.35693 6.00009 7.00334V3.55534L5.33343 2.88867Z',
|
||||
text: 'Lastest product updates?',
|
||||
ctaLabel: 'See Changelog',
|
||||
url: 'https://supabase.com/changelog',
|
||||
},
|
||||
{
|
||||
featherIcon: IconCheckCircle,
|
||||
text: "Something's not right?",
|
||||
ctaLabel: 'Check system status',
|
||||
url: 'https://status.supabase.com/',
|
||||
},
|
||||
]
|
||||
|
||||
export const secondaryLinks = [
|
||||
{ title: 'Contributing', url: '/handbook/contributing' },
|
||||
{
|
||||
title: 'Author Styleguide',
|
||||
url: 'https://github.com/supabase/supabase/blob/master/DEVELOPERS.md',
|
||||
},
|
||||
{ title: 'Open Source', url: 'https://supabase.com/open-source' },
|
||||
{ title: 'SupaSquad', url: 'https://supabase.com/supasquad' },
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Global Flags
|
||||
subtitle: 'Supabase CLI supports global flags for every command.'
|
||||
description: 'Supabase CLI supports global flags for every command.'
|
||||
---
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Layout: FC<Props> = (props: Props) => {
|
||||
const { asPath, basePath } = useRouter()
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const hasTableOfContents =
|
||||
props.toc !== undefined &&
|
||||
@@ -60,26 +60,30 @@ const Layout: FC<Props> = (props: Props) => {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div className={['relative transition-all ease-out', 'duration-150 '].join(' ')}>
|
||||
{/* <p className="text-brand-900 tracking-wider">Tutorials</p> */}
|
||||
<article className="prose dark:prose-dar max-w-none">
|
||||
<h1>{props.meta.title}</h1>
|
||||
{/* <div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-16"></div> */}
|
||||
<MDXProvider components={components} children={props.children} />
|
||||
</article>
|
||||
</div>
|
||||
{hasTableOfContents && !props.meta?.hide_table_of_contents && (
|
||||
<div
|
||||
className={[
|
||||
'border-scale-400 dark:bg-scale-200 table-of-contents-height border-l',
|
||||
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
|
||||
].join(' ')}
|
||||
>
|
||||
<TableOfContents toc={props.toc} video={props.meta?.video} />
|
||||
<LayoutMainContent>
|
||||
<div className={['relative transition-all ease-out', 'duration-150 '].join(' ')}>
|
||||
<article className="prose dark:prose-dar max-w-none">
|
||||
<h1>{props.meta.title}</h1>
|
||||
<MDXProvider components={components} children={props.children} />
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
{hasTableOfContents && !props.meta?.hide_table_of_contents && (
|
||||
<div
|
||||
className={[
|
||||
'border-scale-400 dark:bg-scale-200 table-of-contents-height border-l',
|
||||
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
|
||||
].join(' ')}
|
||||
>
|
||||
<TableOfContents toc={props.toc} video={props.meta.video} />
|
||||
</div>
|
||||
)}
|
||||
</LayoutMainContent>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const LayoutMainContent: FC<{ className?: string }> = ({ className, children }) => (
|
||||
<div className={['max-w-7xl px-5 mx-auto py-16', className].join(' ')}>{children}</div>
|
||||
)
|
||||
|
||||
export default Layout
|
||||
@@ -0,0 +1,86 @@
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FC } from 'react'
|
||||
import components from '~/components'
|
||||
import TableOfContents from '~/components/TableOfContents'
|
||||
import HomePageCover from '../components/HomePageCover'
|
||||
import { LayoutMainContent } from './DefaultLayout'
|
||||
|
||||
interface Props {
|
||||
meta: {
|
||||
title: string
|
||||
description?: string
|
||||
hide_table_of_contents?: boolean
|
||||
video?: string
|
||||
canonical?: string
|
||||
}
|
||||
children: any
|
||||
toc?: any
|
||||
menuItems: any
|
||||
}
|
||||
|
||||
const HomeLayout: FC<Props> = (props: Props) => {
|
||||
const { asPath } = useRouter()
|
||||
|
||||
const hasTableOfContents =
|
||||
props.toc !== undefined &&
|
||||
props.toc.json.filter((item) => item.lvl !== 1 && item.lvl <= 3).length > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{asPath === '/' ? 'Supabase Docs' : `${props.meta?.title} | Supabase Docs`}</title>
|
||||
<meta name="description" content={props.meta?.description} />
|
||||
<meta property="og:image" content={`https://supabase.com/docs/img/supabase-og-image.png`} />
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content={`https://supabase.com/docs/img/supabase-og-image.png`}
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</Head>
|
||||
<NextSeo
|
||||
canonical={props.meta?.canonical ?? `https://supabase.com/docs${asPath}`}
|
||||
openGraph={{
|
||||
url: `https://supabase.com/docs${asPath}`,
|
||||
type: 'article',
|
||||
videos: props.meta?.video && [
|
||||
{
|
||||
// youtube based video meta
|
||||
url: props.meta?.video,
|
||||
width: 640,
|
||||
height: 385,
|
||||
type: 'application/x-shockwave-flash',
|
||||
},
|
||||
],
|
||||
article: {
|
||||
publishedTime: new Date().toISOString(),
|
||||
modifiedTime: new Date().toISOString(),
|
||||
authors: ['Supabase'],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<HomePageCover meta={props.meta} />
|
||||
<LayoutMainContent>
|
||||
<div className={['relative transition-all ease-out', 'duration-150 '].join(' ')}>
|
||||
<article className="prose dark:prose-dar max-w-none">
|
||||
<MDXProvider components={components} children={props.children} />
|
||||
</article>
|
||||
</div>
|
||||
{hasTableOfContents && !props.meta?.hide_table_of_contents && (
|
||||
<div
|
||||
className={[
|
||||
'border-scale-400 dark:bg-scale-200 table-of-contents-height border-l',
|
||||
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
|
||||
].join(' ')}
|
||||
>
|
||||
<TableOfContents toc={props.toc} video={props.meta.video} />
|
||||
</div>
|
||||
)}
|
||||
</LayoutMainContent>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeLayout
|
||||
@@ -339,7 +339,7 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
<MobileHeader />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow px-5 max-w-7xl mx-auto py-16">
|
||||
<div className="grow">
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
@@ -5,20 +5,21 @@ import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { IconExternalLink } from 'ui'
|
||||
import ExpandableVideo from 'ui/src/components/ExpandableVideo/ExpandableVideo'
|
||||
import components from '~/components'
|
||||
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
|
||||
import FooterHelpCallout, { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
|
||||
import { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
|
||||
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
|
||||
import useHash from '~/hooks/useHash'
|
||||
import { LayoutMainContent } from '../DefaultLayout'
|
||||
import ExpandableVideo from 'ui/src/components/ExpandableVideo/ExpandableVideo'
|
||||
|
||||
interface Props {
|
||||
meta: {
|
||||
title: string
|
||||
description?: string
|
||||
description?: string // used in meta tags
|
||||
hide_table_of_contents?: boolean
|
||||
breadcrumb?: string
|
||||
subtitle?: string
|
||||
subtitle?: string // used on the page under the H1
|
||||
footerHelpType?: FooterHelpCalloutType
|
||||
video?: string
|
||||
tocVideo?: string
|
||||
@@ -105,74 +106,75 @@ const Layout: FC<Props> = (props) => {
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div className={['grid grid-cols-12 relative gap-4'].join(' ')}>
|
||||
<div
|
||||
className={[
|
||||
'relative',
|
||||
!props.hideToc ? 'col-span-12 md:col-span-9' : 'col-span-12',
|
||||
'transition-all ease-out',
|
||||
'duration-100',
|
||||
].join(' ')}
|
||||
>
|
||||
{props.meta.breadcrumb && (
|
||||
<p className="text-brand-900 tracking-wider mb-3">{props.meta.breadcrumb}</p>
|
||||
)}
|
||||
<article
|
||||
ref={articleRef}
|
||||
className={`${
|
||||
props.meta?.hide_table_of_contents || !hasTableOfContents ? '' : ''
|
||||
} prose dark:prose-dark max-w-none`}
|
||||
>
|
||||
<h1 className="mb-0">{props.meta.title}</h1>
|
||||
{props.meta.subtitle && (
|
||||
<h2 className="mt-3 text-xl text-scale-1100">{props.meta.subtitle}</h2>
|
||||
)}
|
||||
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
|
||||
<MDXProvider components={components}>{props.children}</MDXProvider>
|
||||
|
||||
{EDIT_BUTTON_EXCLUDE_LIST.includes(router.route) ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="mt-16 not-prose">
|
||||
<div>
|
||||
<Link
|
||||
href={`https://github.com/supabase/supabase/edit/master/apps/docs/pages${router.asPath}.mdx`}
|
||||
passHref
|
||||
>
|
||||
<a className="text-sm transition flex items-center gap-1 text-scale-1000 hover:text-scale-1200">
|
||||
Edit this page on GitHub <IconExternalLink size={14} strokeWidth={1.5} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
</div>
|
||||
{!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && (
|
||||
<LayoutMainContent className="pb-0">
|
||||
<div className={['grid grid-cols-12 relative gap-4'].join(' ')}>
|
||||
<div
|
||||
className={[
|
||||
'col-span-3',
|
||||
'border-scale-400 dark:bg-scale-200 table-of-contents-height',
|
||||
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
|
||||
'relative',
|
||||
!props.hideToc ? 'col-span-12 md:col-span-9' : 'col-span-12',
|
||||
'transition-all ease-out',
|
||||
'duration-100',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="border-l">
|
||||
{props.meta?.tocVideo && !!tocVideoPreview && (
|
||||
<div className="relative mb-6 pl-5">
|
||||
<ExpandableVideo imgUrl={tocVideoPreview} videoId={props.meta.tocVideo} />
|
||||
{props.meta.breadcrumb && (
|
||||
<p className="text-brand-900 tracking-wider mb-3">{props.meta.breadcrumb}</p>
|
||||
)}
|
||||
<article
|
||||
ref={articleRef}
|
||||
className={`${
|
||||
props.meta?.hide_table_of_contents || !hasTableOfContents ? '' : ''
|
||||
} prose dark:prose-dark max-w-none`}
|
||||
>
|
||||
<h1 className="mb-0">{props.meta.title}</h1>
|
||||
{props.meta.subtitle && (
|
||||
<h2 className="mt-3 text-xl text-scale-1100">{props.meta.subtitle}</h2>
|
||||
)}
|
||||
<div className="w-full h-[1px] bg-scale-500 my-8"></div>
|
||||
<MDXProvider components={components}>{props.children}</MDXProvider>
|
||||
|
||||
{EDIT_BUTTON_EXCLUDE_LIST.includes(router.route) ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="mt-16 not-prose">
|
||||
<div>
|
||||
<Link
|
||||
href={`https://github.com/supabase/supabase/edit/master/apps/docs/pages${router.asPath}.mdx`}
|
||||
passHref
|
||||
>
|
||||
<a className="text-sm transition flex items-center gap-1 text-scale-1000 hover:text-scale-1200">
|
||||
Edit this page on GitHub <IconExternalLink size={14} strokeWidth={1.5} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<span className="block font-mono text-xs uppercase text-scale-1200 px-5 mb-6">
|
||||
On this page
|
||||
</span>
|
||||
<GuidesTableOfContents list={tocList} />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<FooterHelpCallout footerHelpType={props.meta?.footerHelpType} title={props.meta?.title} />
|
||||
{!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && (
|
||||
<div
|
||||
className={[
|
||||
'col-span-3',
|
||||
'border-scale-400 dark:bg-scale-200 table-of-contents-height',
|
||||
'thin-scrollbar overflow-y-auto sticky hidden md:block md:col-span-3 px-2',
|
||||
'transition-all ease-out',
|
||||
'duration-100',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="border-l">
|
||||
{props.meta?.tocVideo && !!tocVideoPreview && (
|
||||
<div className="relative mb-6 pl-5">
|
||||
<ExpandableVideo imgUrl={tocVideoPreview} videoId={props.meta.tocVideo} />
|
||||
</div>
|
||||
)}
|
||||
<span className="block font-mono text-xs uppercase text-scale-1200 px-5 mb-6">
|
||||
On this page
|
||||
</span>
|
||||
<GuidesTableOfContents list={tocList} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</LayoutMainContent>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@ interface ISectionDetails {}
|
||||
interface ISectionExamples {}
|
||||
|
||||
const RefSubLayout: FC<RefSubLayoutType> & RefSubLayoutSubComponents = (props) => {
|
||||
return <div className="flex flex-col w-full divide-y">{props.children}</div>
|
||||
return (
|
||||
<div className="flex flex-col w-full divide-y px-5 max-w-7xl mx-auto py-16">
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Section: FC<ISectionContainer> = (props) => {
|
||||
@@ -128,7 +132,7 @@ const StickyHeader: FC<StickyHeader> = ({ icon, ...props }) => {
|
||||
}
|
||||
|
||||
const Details: FC<ISectionDetails> = (props) => {
|
||||
return <div className="relative overflow-hidden w-full">{props.children}</div>
|
||||
return <div className="relative w-full">{props.children}</div>
|
||||
}
|
||||
|
||||
const Examples: FC<ISectionExamples> = (props) => {
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"clsx": "^1.2.1",
|
||||
"common": "*",
|
||||
"config": "*",
|
||||
"framer-motion": "^6.5.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"isbot": "^3.6.5",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
import { AuthProvider, ThemeProvider, useTelemetryProps } from 'common'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { AppPropsWithLayout } from 'types'
|
||||
import { CommandMenuProvider } from 'ui'
|
||||
import Favicons from '~/components/Favicons'
|
||||
@@ -22,17 +22,20 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
IS_PLATFORM || LOCAL_SUPABASE ? createBrowserSupabaseClient() : undefined
|
||||
)
|
||||
|
||||
function handlePageTelemetry(route: string) {
|
||||
return post(`${API_URL}/telemetry/page`, {
|
||||
referrer: document.referrer,
|
||||
title: document.title,
|
||||
route,
|
||||
ga: {
|
||||
screen_resolution: telemetryProps?.screenResolution,
|
||||
language: telemetryProps?.language,
|
||||
},
|
||||
})
|
||||
}
|
||||
const handlePageTelemetry = useCallback(
|
||||
(route: string) => {
|
||||
return post(`${API_URL}/telemetry/page`, {
|
||||
referrer: document.referrer,
|
||||
title: document.title,
|
||||
route,
|
||||
ga: {
|
||||
screen_resolution: telemetryProps?.screenResolution,
|
||||
language: telemetryProps?.language,
|
||||
},
|
||||
})
|
||||
},
|
||||
[telemetryProps]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
function handleRouteChange(url: string) {
|
||||
@@ -59,7 +62,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', handleRouteChange)
|
||||
}
|
||||
}, [router.events])
|
||||
}, [router, handlePageTelemetry])
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
@@ -68,7 +71,26 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
if (router.isReady) {
|
||||
handlePageTelemetry(router.basePath + router.asPath)
|
||||
}
|
||||
}, [router.isReady])
|
||||
}, [router, handlePageTelemetry])
|
||||
|
||||
/**
|
||||
* Reference docs use `history.pushState()` to jump to
|
||||
* sub-sections without causing a re-render.
|
||||
*
|
||||
* We need to the below handler to manually force a re-render
|
||||
* when navigating away from, then back to reference docs
|
||||
*/
|
||||
useEffect(() => {
|
||||
function handler() {
|
||||
router.replace(window.location.href)
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', handler)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('popstate', handler)
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const SITE_TITLE = 'Supabase Documentation'
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const meta = {
|
||||
|
||||
The Next.js Auth Helpers package configures Supabase Auth to store the user's session in a cookie, rather than `localStorage`. This makes the users's session available server-side - in Server Components and Route Handlers - and is automatically sent along with any requests to Supabase.
|
||||
|
||||
> The `app` directory in Next.js is still in beta and expected to change. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs).
|
||||
> Note: If you are using the `pages` directory, check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs).
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
@@ -38,8 +38,6 @@ The Next.js Auth Helpers package configures Supabase Auth to store the user's se
|
||||
npm install @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
> [Next.js Server Components](https://beta.nextjs.org/docs) and the `app` directory are experimental and likely to change.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="yarn" label="Yarn">
|
||||
|
||||
@@ -47,8 +45,6 @@ npm install @supabase/auth-helpers-nextjs
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
> [Next.js Server Components](https://beta.nextjs.org/docs) and the `app` directory are experimental and likely to change.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
@@ -182,7 +178,7 @@ Create a new file at `/app/supabase-provider.tsx` and populate with the followin
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Session, createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
@@ -28,7 +28,7 @@ This library supports the following tooling versions:
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
- Next.js: `>=10`
|
||||
|
||||
> Note: Next.js 13 is stable, however, the new `app` directory and Server Components are still in beta. Check out our experimental guide on [using Auth Helpers with Next.js Server Components](/docs/guides/auth/auth-helpers/nextjs-server-components).
|
||||
> Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the `app` directory and Server Components have reached stable status. Check out our guide on using [Auth Helpers with Next.js Server Components](/docs/guides/auth/auth-helpers/nextjs-server-components).
|
||||
|
||||
Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Setting up Slack logins for your application consists of 3 parts:
|
||||
## Create a Slack OAuth app
|
||||
|
||||
- Go to [api.slack.com](https://api.slack.com/apps).
|
||||
- Click on `Create an App`
|
||||
- Click on `Create New App`
|
||||
|
||||
Under `Create an app...`:
|
||||
|
||||
@@ -43,14 +43,17 @@ Under `App Credentials`:
|
||||
|
||||
- Copy and save your newly-generated `Client ID`
|
||||
- Copy and save your newly-generated `Client Secret`
|
||||
- Click `Permissions`
|
||||
|
||||
Under `Redirect URLs`:
|
||||
Under the sidebar, select `OAuth & Permissions` and look for `Redirect URLs`:
|
||||
|
||||
- Click `Add New Redirect URL`
|
||||
- Paste your `Callback URL` then click `Add`
|
||||
- Click `Save URLs`
|
||||
|
||||
Under `Scopes`:
|
||||
|
||||
- Add the following scopes under the `User Token Scopes`: `profile`, `email`, `openid`. These scopes are the default scopes that Supabase Auth uses to request for user information. You can add any additional scopes that you may need as well.
|
||||
|
||||
## Enter your Slack credentials into your Supabase Project
|
||||
|
||||
<SocialProviderSettingsSupabase provider="Slack" />
|
||||
|
||||
@@ -41,10 +41,18 @@ Under `App credentials`
|
||||
|
||||
- Copy and save your `Client ID`.
|
||||
- Copy and save your `Client secret`.
|
||||
- Add your `Callback URL` in the OAuth allow list.
|
||||
|
||||
Under `Redirect URL for OAuth`
|
||||
|
||||
- Paste your `Callback URL`
|
||||
|
||||
Under `Scopes`
|
||||
|
||||
- Click on `Add scopes`
|
||||
- Click on `User`
|
||||
- Choose `user:read`
|
||||
- Click `Done`
|
||||
- Click `Continue`
|
||||
|
||||
## Enter your Zoom credentials into your Supabase Project
|
||||
|
||||
@@ -7,7 +7,7 @@ export const meta = {
|
||||
'pgvector: a PostgreSQL extension for storing embeddings and performing vector similarity search.',
|
||||
}
|
||||
|
||||
[pgvector](https://github.com/pgvector/pgvector/) is a PostgreSQL extension for vector similarity search. It can also be used for storing [embeddings](https://en.wikipedia.org/wiki/Word_embedding).
|
||||
[pgvector](https://github.com/pgvector/pgvector/) is a PostgreSQL extension for vector similarity search. It can also be used for storing [embeddings](https://supabase.com/blog/openai-embeddings-postgres-vector).
|
||||
|
||||
## Concepts
|
||||
|
||||
@@ -91,9 +91,12 @@ const { data, error } = await supabase.from('posts').insert({
|
||||
})
|
||||
```
|
||||
|
||||
## Resources
|
||||
## More pgvector and Supabase resources
|
||||
|
||||
- Source code: [github.com/pgvector/pgvector](https://github.com/pgvector/pgvector/)
|
||||
- [Supabase Clippy: ChatGPT for Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs)
|
||||
- [Storing OpenAI embeddings in Postgres with pgvector](https://supabase.com/blog/openai-embeddings-postgres-vector)
|
||||
- [A ChatGPT Plugins Template built with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
|
||||
- [Template for building your own custom ChatGPT style doc search](https://github.com/supabase-community/nextjs-openai-doc-search)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ export const meta = {
|
||||
id: 'functions',
|
||||
title: 'Edge Functions',
|
||||
description: 'Globally distributed TypeScript functions.',
|
||||
subtitle: 'Globally distributed TypeScript functions.',
|
||||
sidebar_label: 'Overview',
|
||||
hide_table_of_contents: true,
|
||||
}
|
||||
|
||||
@@ -87,21 +87,17 @@ export const meta = {
|
||||
|
||||
```jsx src/App.jsx
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { createEffect, createSignal, For } from "solid-js";
|
||||
import { createResource, For } from "solid-js";
|
||||
|
||||
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>');
|
||||
|
||||
async function getCountries() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
return data;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [countries, setCountries] = createSignal();
|
||||
|
||||
createEffect(() => {
|
||||
getCountries();
|
||||
});
|
||||
|
||||
async function getCountries() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
setCountries(data);
|
||||
}
|
||||
const [countries] = createResource(getCountries);
|
||||
|
||||
return (
|
||||
<ul>
|
||||
|
||||
@@ -10,16 +10,16 @@ Every project on the Supabase Platform comes with its own dedicated Postgres ins
|
||||
|
||||
| Plan | Pricing | CPU | Memory | Maximum Disk IO Bandwidth | Baseline Disk IO Bandwidth | Connections: Direct (recommended) | Connections: Pooler (recommended) |
|
||||
| --------------- | ------- | ----------------------- | ------ | ------------------------- | -------------------------- | --------------------------------- | --------------------------------- |
|
||||
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 2,606 Mbps | 87 Mbps | 10 | 50 |
|
||||
| Small | $5 | 2-core ARM (shared) | 2 GB | 2,606 Mbps | 174 Mbps | 30 | 75 |
|
||||
| Medium | $50 | 2-core ARM (shared) | 4 GB | 2,606 Mbps | 347 Mbps | 50 | 150 |
|
||||
| Large | $100 | 2-core ARM (dedicated) | 8 GB | 4,750 Mbps | 630 Mbps | 100 | 300 |
|
||||
| XL | $200 | 4-core ARM (dedicated) | 16 GB | 4,750 Mbps | 1,188 Mbps | 200 | 600 |
|
||||
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 4,750 Mbps | 2,375 Mbps | 350 | 1200 |
|
||||
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 4,750 Mbps | 4,750 Mbps | 420 | 2800 |
|
||||
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 9,500 Mbps | 9,500 Mbps | 450 | 5600 |
|
||||
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 14,250 Mbps | 14,250 Mbps | 480 | 8600 |
|
||||
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 19,000 Mbps | 19,000 Mbps | 500 | 11,600 |
|
||||
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 2,606 Mbps | 87 Mbps | 60 | 200 |
|
||||
| Small | $5 | 2-core ARM (shared) | 2 GB | 2,606 Mbps | 174 Mbps | 90 | 200 |
|
||||
| Medium | $50 | 2-core ARM (shared) | 4 GB | 2,606 Mbps | 347 Mbps | 120 | 200 |
|
||||
| Large | $100 | 2-core ARM (dedicated) | 8 GB | 4,750 Mbps | 630 Mbps | 160 | 300 |
|
||||
| XL | $200 | 4-core ARM (dedicated) | 16 GB | 4,750 Mbps | 1,188 Mbps | 240 | 700 |
|
||||
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 4,750 Mbps | 2,375 Mbps | 380 | 1500 |
|
||||
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 4,750 Mbps | 4,750 Mbps | 480 | 3000 |
|
||||
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 9,500 Mbps | 9,500 Mbps | 490 | 6000 |
|
||||
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 14,250 Mbps | 14,250 Mbps | 500 | 9000 |
|
||||
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 19,000 Mbps | 19,000 Mbps | 500 | 12,000 |
|
||||
|
||||
[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ After developing your project and deciding it's Production Ready, you should run
|
||||
- Tools like [k6](https://k6.io/) can simulate traffic from many different users.
|
||||
- Upgrade your database if you require more resources. If you need anything beyond what is listed, contact enterprise@supabase.io.
|
||||
- If you are expecting a surge in traffic (for a big launch) [contact support](https://app.supabase.com/support/new) with more details about your launch. We'll keep an eye on your project.
|
||||
- If you expect your database size to be > 4 GB, [enable](https://app.supabase.com/project/_/settings/billing/update) the Point in Time Recovery (PITR) addon. Daily backups can take up resources from your database when the backup is in progress. PITR is more resource efficient, since only the changes to the database are backed up.
|
||||
|
||||
## Availability
|
||||
|
||||
@@ -47,6 +48,7 @@ After developing your project and deciding it's Production Ready, you should run
|
||||
- Database backups are not available for download on the free tier.
|
||||
- You can set up your own backup systems using tools like [pg_dump](https://www.postgresqltutorial.com/postgresql-backup-database/) or [wal-g](https://github.com/wal-g/wal-g).
|
||||
- Nightly backups for Pro tier projects are available on the Supabase dashboard for up to 7 days.
|
||||
- Point in Time Recovery (PITR) allows a project to be backed up at much shorter intervals. This provides users an option to restore to any chosen point of up to seconds in granularity. In terms of Recovery Point Objective (RPO), Daily Backups would be suitable for projects willing to lose up to 24 hours worth of data. If a lower RPO is required, enable PITR.
|
||||
- Upgrading to the Supabase Pro Tier will give you [access to our support team](https://app.supabase.com/support/new).
|
||||
|
||||
## Rate Limiting, Resource Allocation, & Abuse Prevention
|
||||
|
||||
@@ -93,5 +93,12 @@ pgloader config.load
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
- If you plan on migrating a database larger than 6 GB, we recommend [contacting support](https://app.supabase.com/support/new) to ensure that you'll have the proper disk size pre-provisioned. You can read more about how the disk is managed on Supabase on the [Database usage](/docs/guides/platform/database-size#disk-management).
|
||||
- We also recommend upgrading to at least a [Large instance](/docs/guides/platform/compute-add-ons) for the migration (you can downgrade later) to avoid running out of IO-Budget.
|
||||
|
||||
</Admonition>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
export default Page
|
||||
@@ -67,6 +67,13 @@ psql -h $SUPABASE_HOST -U postgres -f heroku_dump.sql
|
||||
|
||||
Run `pg_dump --help` for a full list of options.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
- If you plan on migrating a database larger than 6 GB, we recommend [contacting support](https://app.supabase.com/support/new) to ensure that you'll have the proper disk size pre-provisioned. You can read more about how the disk is managed on Supabase on the [Database usage](/docs/guides/platform/database-size#disk-management).
|
||||
- We also recommend upgrading to at least a [Large instance](/docs/guides/platform/compute-add-ons) for the migration (you can downgrade later) to avoid running out of IO-Budget.
|
||||
|
||||
</Admonition>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -83,6 +83,13 @@ Run `pg_dump --help` for a full list of options.
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
- If you plan on migrating a database larger than 6 GB, we recommend [contacting support](https://app.supabase.com/support/new) to ensure that you'll have the proper disk size pre-provisioned. You can read more about how the disk is managed on Supabase on the [Database usage](/docs/guides/platform/database-size#disk-management).
|
||||
- We also recommend upgrading to at least a [Large instance](/docs/guides/platform/compute-add-ons) for the migration (you can downgrade later) to avoid running out of IO-Budget.
|
||||
|
||||
</Admonition>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -1,224 +1,199 @@
|
||||
import Layout from '~/layouts/DefaultLayout'
|
||||
import HomeLayout from '~/layouts/HomeLayout'
|
||||
import Link from 'next/link'
|
||||
import { GlassPanel, IconPanel, Button, IconChevronRight } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
GlassPanel,
|
||||
IconBackground,
|
||||
IconChevronRight,
|
||||
IconPanel,
|
||||
IconPlay,
|
||||
TextLink,
|
||||
} from 'ui'
|
||||
import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker'
|
||||
|
||||
export const meta = {
|
||||
title: 'Supabase Docs',
|
||||
title: 'Supabase Documentation',
|
||||
description:
|
||||
'Supabase is an open source Firebase alternative providing all the backend features you need to build a product.',
|
||||
subtitle: 'Supabase Developer Documentation and API Reference',
|
||||
}
|
||||
|
||||
<h2 className="text-base max-w-xl m-0">
|
||||
Supabase is an open source Firebase alternative providing all the backend features you need to
|
||||
build a product. [Learn more](/docs/guides/getting-started/architecture) about Supabase, follow a
|
||||
quickstart for an overview, or dive straight into the different products and APIs.
|
||||
</h2>
|
||||
<div className="flex flex-col">
|
||||
|
||||
{/* start container */}
|
||||
<div>
|
||||
<div className="max-w-xl">
|
||||
## Products
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-12 my-12">
|
||||
<div className="grid grid-cols-12 gap-6 not-prose [&_svg]:text-brand-1100 [&_svg]dark:text-brand-900">
|
||||
{products.map((product) => {
|
||||
return (
|
||||
<Link href={product.href} key={product.title} passHref>
|
||||
<a className={product.span ?? 'col-span-12 md:col-span-4'}>
|
||||
<GlassPanel {...product} icon={<HomeMenuIconPicker icon={product.icon} width={18} height={18} />} background={true} showIconBg={true} showLink={false}>
|
||||
{product.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="
|
||||
border border-scale-400
|
||||
relative overflow-hidden
|
||||
grid grid-cols-12
|
||||
rounded-lg
|
||||
px-8 md:px-12 py-5 md:py-12
|
||||
">
|
||||
<div className="col-span-12 lg:col-span-7 flex flex-col gap-6">
|
||||
<img className="hidden lg:visible absolute right-0 top-0 w-[500px]" src="/docs/img/cards/dark.png" />
|
||||
<div className="">
|
||||
<h3 className="text-2xl m-0 mb-3">Getting Started</h3>
|
||||
<div className="flex flex-col lg:grid grid-cols-12 gap-6 py-12 border-b">
|
||||
|
||||
Discover how to set up a database to an app making queries in just a few minutes.
|
||||
<div className="col-span-4 flex flex-col gap-1 [&_h2]:m-0 [&_h3]:m-0">
|
||||
|
||||
<div className="font-mono uppercase text-xs mb-2">What you will learn</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="text-brand-900">
|
||||
<IconChevronRight size={14} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span className="text-scale-1200">Setting up a Supabase project and app</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="text-brand-900">
|
||||
<IconChevronRight size={14} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span className="text-scale-1200">Adding some sample data to your project</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-3">
|
||||
<div className="text-brand-900">
|
||||
<IconChevronRight size={14} strokeWidth={1.5} />
|
||||
</div>
|
||||
<span className="text-scale-1200">Select() from your new database table</span>
|
||||
<div className="md:max-w-xs 2xl:max-w-none">
|
||||
<div className="flex items-center gap-3 mb-3 text-brand-1100 dark:text-brand-900">
|
||||
{/* <IconBackground>
|
||||
<HomeMenuIconPicker icon="reference-cli" width={18} height={18} />
|
||||
</IconBackground> */}
|
||||
## Client Libraries
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="hidden">
|
||||
<Button size="small">Get started</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-6">
|
||||
<Link href={`/guides/getting-started/quickstarts/reactjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="ReactJS" icon="/docs/img/icons/react-icon" />
|
||||
</div>
|
||||
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{clientLibraries.map((product) => {
|
||||
return (
|
||||
<Link href={product.href} key={product.title} passHref>
|
||||
<a className={product.span ?? 'col-span-6 md:col-span-4'}>
|
||||
<IconPanel
|
||||
{...product}
|
||||
icon={<HomeMenuIconPicker icon={product.icon} width={18} height={18} />}
|
||||
background={true}
|
||||
showIconBg={true}
|
||||
showLink={false}
|
||||
>
|
||||
{product.description}
|
||||
</IconPanel>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:grid grid-cols-12 gap-6 py-12 border-b">
|
||||
|
||||
<div className="col-span-4 flex flex-col gap-1 [&_h2]:m-0">
|
||||
|
||||
## Migrate to Supabase
|
||||
|
||||
<p className="text-scale-1100 text-sm p-0 m-0">
|
||||
Bring your existing data, auth and storage to Supabase following our migration guides.
|
||||
</p>
|
||||
|
||||
<TextLink
|
||||
label="Explore more resources"
|
||||
url="/docs/guides/resources"
|
||||
className="no-underline text-brand-900 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
{migrationGuides.map((product) => {
|
||||
return (
|
||||
<Link
|
||||
href={product.href}
|
||||
key={product.title}
|
||||
passHref
|
||||
>
|
||||
<a className={product.span ?? 'col-span-6 md:col-span-4'}>
|
||||
<IconPanel {...product} background={true} showIconBg={true} showLink={false}>
|
||||
{product.description}
|
||||
</IconPanel>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/redwoodjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="RedwoodJS" icon="/docs/img/icons/redwoodjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/sveltekit`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="SvelteKit" icon="/docs/img/icons/svelte-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/solidjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="SolidJS" icon="/docs/img/icons/solidjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/vue`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Vue" icon="/docs/img/icons/vuejs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-6 py-12 border-b">
|
||||
|
||||
<div className="col-span-4 flex flex-col gap-1 [&_h2]:m-0 [&_h3]:m-0">
|
||||
|
||||
### Additional resources
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{additionalResources.map((product) => {
|
||||
return (
|
||||
<Link
|
||||
href={product.href}
|
||||
key={product.title}
|
||||
passHref
|
||||
>
|
||||
<a className={product.span ?? 'col-span-12 md:col-span-6 lg:col-span-3'}>
|
||||
<GlassPanel {...product} icon={<HomeMenuIconPicker icon={product.icon} width={18} height={18} />} background={false}>
|
||||
{product.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col lg:grid grid-cols-12 gap-6 py-12">
|
||||
|
||||
<div className="col-span-4 flex flex-col gap-1 [&_h2]:m-0 [&_h3]:m-0">
|
||||
|
||||
<div className="md:max-w-xs 2xl:max-w-none">
|
||||
<div className="flex items-center gap-3 mb-3 text-brand-1100 dark:text-brand-900">
|
||||
<IconBackground>
|
||||
<HomeMenuIconPicker icon="self-hosting" width={18} height={18} />
|
||||
</IconBackground>
|
||||
### Self-Hosting
|
||||
</div>
|
||||
<p className="text-scale-1100 text-sm">
|
||||
Get started with self-hosting Supabase.
|
||||
</p>
|
||||
<TextLink
|
||||
label="More on Self-Hosting"
|
||||
url="/docs/guides/self-hosting"
|
||||
className="no-underline text-brand-900 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid col-span-8 grid-cols-12 gap-6 not-prose">
|
||||
<div className="col-span-full lg:col-span-8 grid grid-cols-12 gap-6">
|
||||
{selfHostingOptions.map((product) => {
|
||||
return (
|
||||
<Link
|
||||
href={product.href}
|
||||
key={product.title}
|
||||
passHref
|
||||
>
|
||||
<a className={product.span ?? 'col-span-6'}>
|
||||
<IconPanel {...product} icon={<HomeMenuIconPicker icon={product.icon} width={18} height={18} />} background={true} showIconBg={true} showLink={false}>
|
||||
{product.description}
|
||||
</IconPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div
|
||||
className="
|
||||
border
|
||||
relative
|
||||
px-8 md:px-12 h-20
|
||||
border-scale-400
|
||||
overflow-hidden
|
||||
min-w-full
|
||||
rounded-lg
|
||||
flex flex-row gap-6 items-center
|
||||
"
|
||||
>
|
||||
|
||||
<img
|
||||
className="w-[48px] lg:w-[64px]"
|
||||
src="/docs/img/cards/tutorials-small.svg"
|
||||
/>
|
||||
<img
|
||||
className="z-0 absolute -left-32 -bottom-48 w-[320px]"
|
||||
src="/docs/img/purple-glow-dark.png"
|
||||
/>
|
||||
<div className="
|
||||
flex flex-col md:flex-row not-prose
|
||||
h-full justify-center
|
||||
md:items-center md:gap-3
|
||||
">
|
||||
<h2 className="text-sm lg:text-base text-scale-1200 m-0">Ready to make a fully working app?</h2>
|
||||
<p className="text-sm lg:text-base text-scale-1100 m-0">Follow the step-by-step <Link href="/guides/getting-started#web-app-tutorials"><a className="m-0 text-scale-1200 underline">tutorials</a></Link>.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="max-w-xl">
|
||||
|
||||
### Product Documentation
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{products.map((product) => {
|
||||
return (
|
||||
<Link
|
||||
href={`${product.href}`}
|
||||
key={product.title}
|
||||
passHref
|
||||
>
|
||||
<a className={product.span ?? 'col-span-12 md:col-span-4'}>
|
||||
<GlassPanel {...product} background={true} showIconBg={true} showLink={true}>
|
||||
{product.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
||||
})}
|
||||
|
||||
{platform.map((product) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/${product.href}`}
|
||||
key={product.title}
|
||||
passHref
|
||||
>
|
||||
<a className={'col-span-12 md:col-span-6'}>
|
||||
<GlassPanel {...product} background={false} showIconBg={true}>
|
||||
{product.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
||||
})}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<div className="max-w-xl">
|
||||
|
||||
### Reference Documentation
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{apis.map((product) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/${product.href}`}
|
||||
key={product.title}
|
||||
passHref
|
||||
>
|
||||
<a className={product.span ?? 'col-span-12 lg:col-span-4'}>
|
||||
<GlassPanel {...product} background={false}>
|
||||
{product.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
||||
})}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* end of container */}
|
||||
|
||||
</div>
|
||||
|
||||
export const products = [
|
||||
{
|
||||
title: 'Database',
|
||||
icon: '/docs/img/icons/menu/database',
|
||||
icon: 'database',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/database',
|
||||
description:
|
||||
@@ -227,16 +202,16 @@ export const products = [
|
||||
},
|
||||
{
|
||||
title: 'Auth',
|
||||
icon: '/docs/img/icons/menu/auth',
|
||||
icon: 'auth',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/auth/overview',
|
||||
href: '/guides/auth',
|
||||
description:
|
||||
'Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.',
|
||||
span: 'col-span-12 md:col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Storage',
|
||||
icon: '/docs/img/icons/menu/storage',
|
||||
icon: 'storage',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/storage',
|
||||
description:
|
||||
@@ -244,7 +219,7 @@ export const products = [
|
||||
},
|
||||
{
|
||||
title: 'Realtime',
|
||||
icon: '/docs/img/icons/menu/realtime',
|
||||
icon: 'realtime',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/realtime',
|
||||
description:
|
||||
@@ -252,7 +227,7 @@ export const products = [
|
||||
},
|
||||
{
|
||||
title: 'Edge Functions',
|
||||
icon: '/docs/img/icons/menu/functions',
|
||||
icon: 'functions',
|
||||
hasLightIcon: true,
|
||||
href: '/guides/functions',
|
||||
description:
|
||||
@@ -260,105 +235,122 @@ export const products = [
|
||||
},
|
||||
]
|
||||
|
||||
export const apis = [
|
||||
export const migrationGuides = [
|
||||
{
|
||||
title: 'JavaScript Client Library',
|
||||
icon: '/docs/img/icons/menu/reference-javascript',
|
||||
hasLightIcon: true,
|
||||
title: 'Firebase Auth',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/firebase-auth',
|
||||
},
|
||||
{
|
||||
title: 'Firestore Data',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/firestore-data',
|
||||
},
|
||||
{
|
||||
title: 'Firebase Storage',
|
||||
icon: '/docs/img/icons/firebase-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/firebase-storage',
|
||||
},
|
||||
{
|
||||
title: 'Heroku',
|
||||
icon: '/docs/img/icons/heroku-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/heroku',
|
||||
},
|
||||
{
|
||||
title: 'Render',
|
||||
icon: '/docs/img/icons/render-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/render',
|
||||
},
|
||||
{
|
||||
title: 'Amazon RDS',
|
||||
icon: '/docs/img/icons/aws-rds-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/amazon-rds',
|
||||
},
|
||||
]
|
||||
|
||||
export const selfHostingOptions = [
|
||||
{
|
||||
title: 'Auth',
|
||||
icon: 'auth',
|
||||
href: 'reference/self-hosting-auth/introduction',
|
||||
},
|
||||
{
|
||||
title: 'Realtime',
|
||||
icon: 'realtime',
|
||||
href: 'reference/self-hosting-realtime/introduction',
|
||||
},
|
||||
{
|
||||
title: 'Storage',
|
||||
icon: 'storage',
|
||||
href: 'reference/self-hosting-storage/introduction',
|
||||
},
|
||||
{
|
||||
title: 'Analytics',
|
||||
icon: 'analytics',
|
||||
href: 'reference/self-hosting-analytics/introduction',
|
||||
},
|
||||
]
|
||||
|
||||
export const clientLibraries = [
|
||||
{
|
||||
title: 'Javascript',
|
||||
icon: 'reference-javascript',
|
||||
href: 'reference/javascript/introduction',
|
||||
description: 'Official client libraries for JavaScript and TypeScript.',
|
||||
span: 'col-span-12 md:col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Flutter Client Library',
|
||||
icon: '/docs/img/icons/menu/reference-dart',
|
||||
hasLightIcon: true,
|
||||
title: 'Flutter',
|
||||
icon: 'reference-dart',
|
||||
href: 'reference/dart/introduction',
|
||||
description: 'Official client libraries for Flutter and Dart.',
|
||||
span: 'col-span-12 md:col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Python',
|
||||
icon: 'reference-python',
|
||||
href: 'reference/python/introduction',
|
||||
},
|
||||
{
|
||||
title: 'C#',
|
||||
icon: 'reference-csharp',
|
||||
href: 'reference/csharp/introduction',
|
||||
},
|
||||
{
|
||||
title: 'Swift',
|
||||
icon: 'reference-swift',
|
||||
href: 'reference/swift/introduction',
|
||||
},
|
||||
{
|
||||
title: 'Kotlin',
|
||||
icon: 'reference-kotlin',
|
||||
href: 'reference/kotlin/introduction',
|
||||
},
|
||||
]
|
||||
|
||||
export const additionalResources = [
|
||||
{
|
||||
title: 'Management API',
|
||||
icon: '/docs/img/icons/menu/reference-api',
|
||||
hasLightIcon: true,
|
||||
description: 'Manage your Supabase projects and organizations.',
|
||||
icon: 'reference-api',
|
||||
href: 'reference/api/introduction',
|
||||
description: 'Manage your Supabase projects and organizations programmatically.',
|
||||
span: 'col-span-12 md:col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Supabase CLI',
|
||||
icon: '/docs/img/icons/menu/reference-api',
|
||||
hasLightIcon: true,
|
||||
description: 'Use the CLI to develop, manage and deploy your projects.',
|
||||
icon: 'reference-cli',
|
||||
href: 'reference/cli/introduction',
|
||||
description:
|
||||
'Use the CLI to develop your project locally, manage database migrations, and deploy to the Supabase Platform or self-host.',
|
||||
span: 'col-span-12 md:col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Self-Hosting Auth',
|
||||
icon: '/docs/img/icons/menu/reference-auth',
|
||||
hasLightIcon: true,
|
||||
href: 'reference/self-hosting-auth/introduction',
|
||||
description: 'Self-host your own Auth server.',
|
||||
},
|
||||
{
|
||||
title: 'Self-Hosting Storage',
|
||||
icon: '/docs/img/icons/menu/reference-storage',
|
||||
hasLightIcon: true,
|
||||
href: 'reference/self-hosting-storage/introduction',
|
||||
description: 'Self-host your own Storage server.',
|
||||
},
|
||||
{
|
||||
title: 'Self-Hosting Realtime',
|
||||
icon: '/docs/img/icons/menu/reference-realtime',
|
||||
hasLightIcon: true,
|
||||
href: 'reference/self-hosting-realtime/introduction',
|
||||
description: 'Self-host your own Realtime server.',
|
||||
},
|
||||
{
|
||||
title: 'Self-Hosting Analytics',
|
||||
icon: '/docs/img/icons/menu/reference-analytics',
|
||||
hasLightIcon: true,
|
||||
href: 'reference/self-hosting-analytics/introduction',
|
||||
description: 'Self-host your own Analytics server.',
|
||||
},
|
||||
]
|
||||
|
||||
export const platform = [
|
||||
{
|
||||
// header: '/docs/img/cards/sample-card-header-3.svg',
|
||||
title: 'Platform Guides',
|
||||
icon: '/docs/img/icons/menu/platform',
|
||||
hasLightIcon: true,
|
||||
description: 'Learn more about the tools and services powering Supabase.',
|
||||
icon: 'platform',
|
||||
href: 'guides/platform',
|
||||
description:
|
||||
'Learn more about the tools and services powering the Supabase Platform, available add-ons, and how to customize the platform for your needs.',
|
||||
},
|
||||
{
|
||||
// header: '/docs/img/cards/sample-card-header-2.svg',
|
||||
title: 'Resources',
|
||||
icon: '/docs/img/icons/menu/resources',
|
||||
hasLightIcon: true,
|
||||
href: 'guides/resources',
|
||||
description: 'View examples, curated content from the community, migration guides, and more.',
|
||||
},
|
||||
{
|
||||
title: 'Self-Hosting Supabase',
|
||||
icon: '/docs/img/icons/menu/platform',
|
||||
hasLightIcon: true,
|
||||
href: 'guides/self-hosting',
|
||||
description: 'Learn how to self-host Supabase and deploy to your own infrastructure.',
|
||||
},
|
||||
{
|
||||
// header: '/docs/img/cards/sample-card-header-2.svg',
|
||||
title: 'Integrations',
|
||||
icon: '/docs/img/icons/menu/integrations',
|
||||
hasLightIcon: true,
|
||||
href: 'guides/integrations',
|
||||
description: 'Explore a variety of integrations from Supabase partners.',
|
||||
icon: 'integrations',
|
||||
href: 'guides/integrations',
|
||||
},
|
||||
]
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
export const Page = ({ children }) => <HomeLayout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -25,7 +25,7 @@ export default function Config() {
|
||||
<Head>
|
||||
<title>Supabase CLI config</title>
|
||||
</Head>
|
||||
<div className="grid grid-cols-12 relative gap-4">
|
||||
<div className="grid grid-cols-12 relative gap-4 px-5 max-w-7xl mx-auto py-16">
|
||||
<div className="relative col-span-12 md:col-span-9 transition-all ease-out duration-100">
|
||||
<div className="w-full prose">
|
||||
<h1 className="">CLI configuration</h1>
|
||||
|
||||
@@ -9,7 +9,15 @@ const sections = flattenSections(clientLibsCommonSections)
|
||||
const libraryPath = '/dart/v0'
|
||||
|
||||
export default function JSReference(props) {
|
||||
return <RefSectionHandler sections={sections} spec={spec} pageProps={props} type="client-lib" />
|
||||
return (
|
||||
<RefSectionHandler
|
||||
sections={sections}
|
||||
spec={spec}
|
||||
pageProps={props}
|
||||
type="client-lib"
|
||||
isOldVersion
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { inspect } from 'util'
|
||||
import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json'
|
||||
import typeSpec from '~/../../spec/enrichments/tsdoc_v1/combined.json'
|
||||
import spec from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
|
||||
@@ -18,6 +17,7 @@ export default function JSReference(props) {
|
||||
typeSpec={typeSpec}
|
||||
pageProps={props}
|
||||
type="client-lib"
|
||||
isOldVersion
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.99982 10.0368H12.9998V14.0368H2.99982V10.0368Z" fill="#133929" stroke="#4CC38A" stroke-width="0.636673" stroke-miterlimit="10" stroke-linejoin="bevel"/>
|
||||
<rect x="3.50189" y="5.9998" width="8.99609" height="4" stroke="#4CC38A" stroke-width="0.636673" stroke-miterlimit="10" stroke-linejoin="bevel"/>
|
||||
<path d="M2.99982 2.00008H12.9998V6.00008H2.99982V2.00008Z" fill="#133929" stroke="#4CC38A" stroke-width="0.636673" stroke-miterlimit="10" stroke-linejoin="bevel"/>
|
||||
<path d="M2.99982 10.0368H12.9998V14.0368H2.99982V10.0368Z" fill="#133929" stroke="#4CC38A" stroke-width="1" stroke-miterlimit="10" stroke-linejoin="bevel"/>
|
||||
<rect x="3.50189" y="5.9998" width="8.99609" height="4" stroke="#4CC38A" stroke-width="1" stroke-miterlimit="10" stroke-linejoin="bevel"/>
|
||||
<path d="M2.99982 2.00008H12.9998V6.00008H2.99982V2.00008Z" fill="#133929" stroke="#4CC38A" stroke-width="1" stroke-miterlimit="10" stroke-linejoin="bevel"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 558 B |
@@ -96,6 +96,23 @@ export function splitTreeBy(tree: Root, predicate: (node: Content) => boolean) {
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a markdown heading which can optionally
|
||||
* contain a custom anchor in the format:
|
||||
*
|
||||
* ```markdown
|
||||
* ### My Heading [#my-custom-anchor]
|
||||
* ```
|
||||
*/
|
||||
export function parseHeading(heading: string): { heading: string; customAnchor?: string } {
|
||||
const match = heading.match(/(.*) *\[#(.*)\]/)
|
||||
if (match) {
|
||||
const [, heading, customAnchor] = match
|
||||
return { heading, customAnchor }
|
||||
}
|
||||
return { heading }
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes MDX content for search indexing.
|
||||
* It extracts metadata, strips it of all JSX,
|
||||
@@ -140,12 +157,20 @@ export function processMdxForSearch(content: string): ProcessedMdx {
|
||||
|
||||
const sections = sectionTrees.map((tree) => {
|
||||
const [firstNode] = tree.children
|
||||
const content = toMarkdown(tree)
|
||||
|
||||
const heading = firstNode.type === 'heading' ? toString(firstNode) : undefined
|
||||
const slug = heading ? slugger.slug(heading) : undefined
|
||||
const rawHeading: string = firstNode.type === 'heading' ? toString(firstNode) : undefined
|
||||
|
||||
if (!rawHeading) {
|
||||
return { content }
|
||||
}
|
||||
|
||||
const { heading, customAnchor } = parseHeading(rawHeading)
|
||||
|
||||
const slug = slugger.slug(customAnchor ?? heading)
|
||||
|
||||
return {
|
||||
content: toMarkdown(tree),
|
||||
content,
|
||||
heading,
|
||||
slug,
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ article h1 {
|
||||
@apply text-scale-900 text-xs;
|
||||
}
|
||||
|
||||
.prose :where(p):not(:where([class~='not-prose'] *)) {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
text-shadow: none !important;
|
||||
|
||||
@@ -12,6 +12,16 @@ date: '2022-06-30'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🚨 We are currently running our first-ever Flutter Hackathon, in partnership with FlutterFlow and Invertase 🚨
|
||||
|
||||
Build and submit an app till 11:59 pm PT on Sunday, May 21st and win limited edition swag!
|
||||
|
||||
[Learn more.](https://supabase.com/blog/flutter-hackathon)
|
||||
|
||||
</div>
|
||||
|
||||
This is the first of a series of Flutter Tutorials. During the series, you will learn how to build cross-platform apps without worrying about the backend.
|
||||
|
||||
In the first part, I will show you how to build a chat application, where users can sign up and talk to other users in a single chat room. The exchange of messages will happen in real time, meaning that you do not have to refresh the page to load new messages.
|
||||
|
||||
@@ -11,6 +11,16 @@ date: '2022-08-02'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🚨 We are currently running our first-ever Flutter Hackathon, in partnership with FlutterFlow and Invertase 🚨
|
||||
|
||||
Build and submit an app till 11:59 pm PT on Sunday, May 21st and win limited edition swag!
|
||||
|
||||
[Learn more.](https://supabase.com/blog/flutter-hackathon)
|
||||
|
||||
</div>
|
||||
|
||||
Today, we are releasing of Developer Preview version of v1.0 of [Supabase Flutter SDK](https://pub.dev/packages/supabase_flutter/versions/1.0.0-dev.1). Flutter has quickly become one of the most popular frameworks for developers to build cross-platform mobile apps. We can attest to that growth, our Flutter SDK is one of the most popular libraries and each day we see more Flutter devs choosing Supabase.
|
||||
|
||||
For this release, our main focus is developer experiences. We would love for you to try the SDK and provide your feedback so that we can continue to improve!
|
||||
|
||||
@@ -11,6 +11,16 @@ date: '2022-10-21'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🚨 We are currently running our first-ever Flutter Hackathon, in partnership with FlutterFlow and Invertase 🚨
|
||||
|
||||
Build and submit an app till 11:59 pm PT on Sunday, May 21st and win limited edition swag!
|
||||
|
||||
[Learn more.](https://supabase.com/blog/flutter-hackathon)
|
||||
|
||||
</div>
|
||||
|
||||
A few months ago, we announced a [developer preview version of supabase-flutter SDK](https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview). Since then, we have heard a lot of amazing feedback from the community, and have been improving it. Today, we are happy to announce the stable v1 of [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can also find the updated [quick start guide](https://supabase.com/docs/guides/with-flutter), [documentation](https://supabase.com/docs/reference/dart) and a [migration guide from v0](https://supabase.com/docs/reference/dart/v0/upgrade-guide).
|
||||
|
||||
## What is new in v1?
|
||||
|
||||
@@ -12,6 +12,16 @@ date: '2022-11-22'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🚨 We are currently running our first-ever Flutter Hackathon, in partnership with FlutterFlow and Invertase 🚨
|
||||
|
||||
Build and submit an app till 11:59 pm PT on Sunday, May 21st and win limited edition swag!
|
||||
|
||||
[Learn more.](https://supabase.com/blog/flutter-hackathon)
|
||||
|
||||
</div>
|
||||
|
||||
This article is the second part of the Flutter tutorial series. During the series, you will learn how to build cross-platform apps without worrying about the backend.
|
||||
|
||||
In this article, I will show you how you can make a secure chat application by introducing authentication and authorization to the basic chat app that we created [previously](https://supabase.com/blog/flutter-tutorial-building-a-chat-app).
|
||||
|
||||
@@ -12,7 +12,7 @@ date: '2023-02-06'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
A new PostgreSQL extension is now available in Supabase: [`pgvector`](https://github.com/pgvector/pgvector), an open-source vector similarity search.
|
||||
A new PostgreSQL extension is now available in Supabase: [`pgvector`](https://supabase.com/docs/guides/getting-started/openai/vector-search), an open-source vector similarity search.
|
||||
|
||||
The exponential progress of AI functionality over the past year has inspired many new real world applications. One specific challenge has been the ability to store and query _embeddings_ at scale.
|
||||
In this post we'll explain what embeddings are, why we might want to use them, and how we can store and query them in PostgreSQL using `pgvector`.
|
||||
@@ -145,9 +145,9 @@ Once your table starts to grow with embeddings, you will likely want to add an i
|
||||
Each distance operator requires a different type of index. We expect to order by cosine distance, so we need `vector_cosine_ops` index. A good starting number of lists is 4 \* sqrt(table_rows):
|
||||
|
||||
```sql
|
||||
create index on documents
|
||||
using ivfflat (embedding vector_cosine_ops)
|
||||
with (lists = 100);
|
||||
create index on documents using ivfflat (embedding vector_cosine_ops)
|
||||
with
|
||||
(lists = 100);
|
||||
```
|
||||
|
||||
You can read more about indexing on `pgvector`'s GitHub page [here](https://github.com/pgvector/pgvector#indexing).
|
||||
@@ -370,6 +370,11 @@ The OpenAI API supports [completion streaming](https://platform.openai.com/docs/
|
||||
|
||||
Storing embeddings in Postgres opens a world of possibilities. You can combine your search function with telemetry functions, add an user-provided feedback (thumbs up/down), and make your search feel more integrated with your products.
|
||||
|
||||
Tomorrow we'll be releasing our own version of this interface in the Supabase docs, to provide Supabase-specific answers.
|
||||
The [pgvector extension](https://supabase.com/docs/guides/getting-started/openai/vector-search) is available on all new Supabase projects today. If you want to try it out, launch a new Postgres database today: [database.new](https://database.new)
|
||||
|
||||
The pgvector extension is available on all new Supabase projects today. If you want to try it out, launch a new Postgres database today: [database.new](https://database.new)
|
||||
## More pgvector and ChatGPT resources
|
||||
|
||||
- [Supabase Clippy: ChatGPT for Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs)
|
||||
- [A ChatGPT Plugins Template built with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
|
||||
- [Template for building your own custom ChatGPT style doc search](https://github.com/supabase-community/nextjs-openai-doc-search)
|
||||
- [Supabase + LangChain Starter Template](https://blog.langchain.dev/langchain-x-supabase/)
|
||||
@@ -11,6 +11,12 @@ date: '2023-02-07'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🆕 Want to build your own Clippy based on your own content? Use our [Next.js OpenAI Doc Search Template](https://github.com/supabase-community/nextjs-openai-doc-search) to deploy your own now!
|
||||
|
||||
</div>
|
||||
|
||||
We all know that Microsoft's real agenda for pouring billions into OpenAI is to revive their favorite friend Clippy.
|
||||
Today, we're doing our part to support the momentum by releasing “Supabase Clippy” for our docs (and we don't expect this name to last long before the lawyers catch on).
|
||||
|
||||
@@ -113,8 +119,9 @@ While you're at it, consider supporting [@ankane](https://github.com/ankane)'s g
|
||||
|
||||
Want to try Supabase Clippy? It's a hidden feature while in MVP - visit [supabase.com/docs](/docs) and hit `cmd` + `/` to pull up the search box (sorry mobile users).
|
||||
|
||||
If you have any comments we'd love to hear them in our [ShowHN](https://news.ycombinator.com/item?id=34695306).
|
||||
## More pgvector and ChatGPT resources
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Want to build your own Clippy based on your own content? Use our [Next.js OpenAI Doc Search Template](https://github.com/supabase-community/nextjs-openai-doc-search) to deploy your own now!
|
||||
- [pgvector docs]([https://supabase.com/blog/chatgpt-supabase-docs](https://supabase.com/docs/guides/getting-started/openai/vector-search)
|
||||
- [A ChatGPT Plugins Template built with Supabase Edge Runtime](https://supabase.com/blog/building-chatgpt-plugins-template)
|
||||
- [Template for building your own custom ChatGPT style doc search](https://github.com/supabase-community/nextjs-openai-doc-search)
|
||||
- [Supabase + LangChain Starter Template](https://blog.langchain.dev/langchain-x-supabase/)
|
||||
@@ -12,6 +12,16 @@ date: '2023-02-14'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🚨 We are currently running our first-ever Flutter Hackathon, in partnership with FlutterFlow and Invertase 🚨
|
||||
|
||||
Build and submit an app till 11:59 pm PT on Sunday, May 21st and win limited edition swag!
|
||||
|
||||
[Learn more.](https://supabase.com/blog/flutter-hackathon)
|
||||
|
||||
</div>
|
||||
|
||||
Flutter is a UI library to build apps that run on any platform, but it can also build interactive games thanks to an open-source game engine built on top of Flutter called [Flame](https://flame-engine.org/). Flame takes care of things like collision detection or loading image sprites to bring game development to all the Flutter devs. We can take it a step further to introduce real-time communication features so that players can play against each other in real-time.
|
||||
|
||||
In this article, we will use Flutter, Flame, and Supabase's real-time features to build a real-time multiplayer shooting game. You can find the complete code of this tutorial [here](https://github.com/supabase/supabase/tree/master/examples/realtime/flutter-multiplayer-shooting-game).
|
||||
|
||||
@@ -8,7 +8,7 @@ launchweek: 7
|
||||
tags:
|
||||
- tutorial
|
||||
- launch-week
|
||||
- Next-js
|
||||
- Next.js
|
||||
date: '2023-04-04'
|
||||
toc_depth: 6
|
||||
---
|
||||
|
||||
@@ -225,6 +225,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
Since this is a major update that touches many of the authentication routes, we will roll it out gradually over the next few weeks. You will receive a notification in your dashboard when the feature is available for your project. Reach out to us if you want early access to this feature.
|
||||
|
||||
**Update**: Server-Side Auth (PKCE) is now available on all projects. Please refer to our [Server Side Auth Guide](https://supabase.com/docs/guides/auth/server-side-rendering) for further details on how to add PKCE to your project.
|
||||
|
||||
## Native Apple login on iOS
|
||||
|
||||
While PKCE support is great, that is not the only news for you mobile app developers out there.
|
||||
|
||||
@@ -10,6 +10,16 @@ date: '2023-05-04'
|
||||
toc_depth: 2
|
||||
---
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
🚨 We are currently running our first-ever Flutter Hackathon, in partnership with FlutterFlow and Invertase 🚨
|
||||
|
||||
Build and submit an app till 11:59 pm PT on Sunday, May 21st and win limited edition swag!
|
||||
|
||||
[Learn more.](https://supabase.com/blog/flutter-hackathon)
|
||||
|
||||
</div>
|
||||
|
||||
Multi-factor authentication or MFA is an essential part of security for any kind of app.
|
||||
|
||||
We will take a look at an example app where a user has to sign in via MFA in order to view the contents of the app to demonstrate how easy it is to get started with MFA on Flutter.
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: 'Flutter Hackathon'
|
||||
description: Build Flutter apps and win limited edition swag.
|
||||
author: tyler_shukert
|
||||
image: flutter-hackathon/thumbnail.jpg
|
||||
thumb: flutter-hackathon/thumbnail.jpg
|
||||
tags:
|
||||
- flutter
|
||||
- hackathon
|
||||
date: '2023-05-12'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
As new Flutter updates are announced at Google I/O, we are excited to kick off the Flutter hackathon with the help of our partners, [FlutterFlow](https://flutterflow.io/) and [Invertase](https://invertase.io/). The rules are simple: build beautiful apps using our favorite hybrid framework, Flutter!
|
||||
|
||||

|
||||
|
||||
## Key Facts
|
||||
|
||||
- You have 10 days to build a new app using Flutter/Dart and Supabase in some capacity.
|
||||
- Starting 7:00 am PT Friday, May 12th, 2023
|
||||
- The submission deadline is 11:59 pm PT on Sunday, May 21st, 2023
|
||||
- Enter as an individual, or as a team of up to 5 people
|
||||
|
||||
## Prizes
|
||||
|
||||
There are 5 categories, there will be prizes for:
|
||||
|
||||
- Best overall project (Mechanical Keyboards & Swag kit)
|
||||
- Best FlutterFlow project (FlutterFlow Swag kit & 3 months of FlutterFlow Pro plan for free)
|
||||
- Best Dart Edge Project (Swag kit)
|
||||
- Most technically impressive (Swag kit)
|
||||
- Most visually pleasing (Swag kit)
|
||||
|
||||
There will be a winner and a runner-up prize for each category. Every team member on winning/runner-up teams gets a swag kit.
|
||||
|
||||

|
||||
|
||||
## Submission
|
||||
|
||||
Please include an easily accessible demo, or a demo video so that the judges can take a look at the app. For Flutter apps, [Zapp](https://zapp.run/) is a great tool to generate an easily shareable demo link for your app. For a FlutterFlow project, please share a publicly shareable link so that the judges can take a look at the actual app.
|
||||
|
||||
You should submit your project using [this form](https://www.madewithsupabase.com/flutter-hackathon) before 11:59 pm Sunday midnight PT May 21st, 2023.
|
||||
|
||||
## Judges
|
||||
|
||||
[Pooja](https://twitter.com/pooja_bhaumik) from FlutterFlow, [Majid](https://twitter.com/mhadaily) from Invertase, and [Tyler](https://twitter.com/dshukertjr), myself from Supabase will do the honors of judging the winners.
|
||||
We will be looking for:
|
||||
|
||||
- creativity/inventiveness
|
||||
- functions correctly/smoothly
|
||||
- visually pleasing
|
||||
- technically impressive
|
||||
- use of Supabase features
|
||||
- deep usage of a single feature or
|
||||
- broad usage are both ok
|
||||
- FUN! 😃
|
||||
|
||||
## Rules
|
||||
|
||||
- Team size 1-5 (all team members on winning teams will receive a prize)
|
||||
- You cannot be on multiple teams
|
||||
- One submission per team
|
||||
- All design elements, code, etc. for your project must be created **during** the event
|
||||
- Must use Flutter and Supabase in some capacity
|
||||
- You must submit before the deadline (no late entries)
|
||||
|
||||
## Resources
|
||||
|
||||
### Supabase resources
|
||||
|
||||
- [Flutter Supabase quick starter guide](https://supabase.com/docs/guides/getting-started/tutorials/with-flutter)
|
||||
- [Setup Supabase on FlutterFlow docs](https://docs.flutterflow.io/data-and-backend/supabase/supabase-setup)
|
||||
- [How to setup Supabase auth on FlutterFlow video](https://youtu.be/tL-sLPfWzVE)
|
||||
- [Dart Edge Guide](https://supabase.com/docs/guides/functions/dart-edge)
|
||||
- [Getting started with Dart Edge video](https://youtu.be/53jhgrDONW4)
|
||||
|
||||
### Community help
|
||||
|
||||
The Supabase Team will be taking part in the Hackathon and you'll find us live to build in our discord all week. Please join us by building in public:
|
||||
|
||||
- Text channel: [#hackathon](https://discord.gg/UYyweApy)
|
||||
|
||||
If you need help or advice when building, find other people to join your team, or if you just want to chill and watch people build, come and join us!
|
||||
|
||||
[Join our Discord](https://discord.supabase.com/)
|
||||
|
||||

|
||||
|
||||
## Additional Info
|
||||
|
||||
- Any intellectual property developed during the hackathon will belong to the team that developed it. We expect that each team will have an agreement between themselves regarding the IP, but this is not required
|
||||
- By making a submission you grant Supabase permission to use screenshots, code snippets, and/or links to your project or content of your README on our Twitter, blog, website, email updates, and in the Supabase discord server. Supabase does not make any claims over your IP.
|
||||
- Flutter and the related logo are trademarks of Google LLC. Supabase Flutter Hackathon is not affiliated with or otherwise sponsored by Google LLC.
|
||||
@@ -0,0 +1,278 @@
|
||||
---
|
||||
title: 'Building ChatGPT Plugins with Supabase Edge Runtime'
|
||||
tags:
|
||||
- AI
|
||||
- tutorial
|
||||
- functions
|
||||
date: '2023-05-15'
|
||||
toc_depth: 2
|
||||
author: thor_schaeff
|
||||
image: 2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_template_deno.jpeg
|
||||
thumb: 2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_template_deno.jpeg
|
||||
---
|
||||
|
||||
ChatGPT Plugins support is [rolling out in beta](https://help.openai.com/en/articles/6825453-chatgpt-release-notes) this week! To help you get up and running quickly, we're releasing a [plugin template](https://github.com/supabase-community/chatgpt-plugin-template-deno) written in TypeScript and running on [Supabase Edge Runtime](https://supabase.com/blog/edge-runtime-self-hosted-deno-functions)!
|
||||
|
||||
Want to get started right away? [Fork the template on GitHub](https://github.com/supabase-community/chatgpt-plugin-template-deno)!
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
className="w-full"
|
||||
src="https://www.youtube-nocookie.com/embed/4pa-eEXQHJQ"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Serving the manifest file
|
||||
|
||||
The `ai-plugin.json` [manifest file](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest) is required for ChatGPT to identify our plugin, know what kind of authentication mechanism is used, understand where to find the OpenAPI definition, and some other details about our plugin. You can find the full list of supported parameters in the [OpenAI docs](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest).
|
||||
|
||||
Supabase Edge Runtime does currently not support hosting/serving of static files, however, we can import JSON files in our function and serve them as a JSON response. As this needs to be at the root of our domain, we add this to our [main function handler](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/functions/main/index.ts#L69-L74):
|
||||
|
||||
```ts
|
||||
// functions/main/index.ts
|
||||
import aiPlugins from './ai-plugins.json' assert { type: 'json' }
|
||||
|
||||
// [...]
|
||||
|
||||
// Serve /.well-known/ai-plugin.json
|
||||
if (service_name === '.well-known') {
|
||||
return new Response(JSON.stringify(aiPlugins), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Now, when running Edge Runtime locally via Docker, our plugin manifest will be available at [http://localhost:8000/.well-known/ai-plugin.json](http://localhost:8000/.well-known/ai-plugin.json)
|
||||
|
||||
## Generating the OpenAPI definition with swagger-jsdoc
|
||||
|
||||
The [OpenAPI definition](https://platform.openai.com/docs/plugins/getting-started/openapi-definition) is required for ChatGPT to know how to underact with our API. Only endpoints included in there will be exposed to ChatGPT, which allows you to selectively make our endpoints available, or add specific endpoints for ChatGPT.
|
||||
|
||||
The OpenAPI definition can be either in YAML or JSON format. We’ll be using JSON and the same approach as above to serve it. Writing an OpenAPI definition is not something we will want to do by hand, luckily there is an open source tool called [swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc) which we can use to annotate our endpoints with JSDoc comments and generate the OpenAPI definition with a [little script](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/scripts/generate-openapi-spec.ts).
|
||||
|
||||
```ts
|
||||
// /scripts/generate-openapi-spec.ts
|
||||
import swaggerJsdoc from 'npm:swagger-jsdoc@6.2.8'
|
||||
|
||||
const options = {
|
||||
definition: {
|
||||
openapi: '3.0.1',
|
||||
info: {
|
||||
title: 'TODO Plugin',
|
||||
description: `A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username "global".`,
|
||||
version: '1.0.0',
|
||||
},
|
||||
servers: [{ url: 'http://localhost:8000' }],
|
||||
},
|
||||
apis: ['./functions/chatgpt-plugin/index.ts'], // files containing annotations as above
|
||||
}
|
||||
|
||||
const openapiSpecification = swaggerJsdoc(options)
|
||||
const openapiString = JSON.stringify(openapiSpecification, null, 2)
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(openapiString)
|
||||
await Deno.writeFile('./functions/chatgpt-plugin/openapi.json', data)
|
||||
console.log(openapiString)
|
||||
```
|
||||
|
||||
Since this script is run outside of the function execution, e.g. as a GitHub Action, we can use [npm specifiers](https://deno.com/manual/node/npm_specifiers) to import `swagger-jsdoc`.
|
||||
|
||||
Next, we create our `/functions/chatgpt-plugin/index.ts` [file](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/functions/chatgpt-plugin/index.ts) where we use the [Deno oak router](https://deno.land/x/oak) to build our API and annotate it with JSDOC comments.
|
||||
|
||||
```ts
|
||||
// /functions/chatgpt-plugin/index.ts
|
||||
import { Application, Router } from 'https://deno.land/x/oak@v11.1.0/mod.ts'
|
||||
import openapi from './openapi.json' assert { type: 'json' }
|
||||
|
||||
console.log('Hello from `chatgpt-plugin` Function!')
|
||||
|
||||
const _TODOS: { [key: string]: Array<string> } = {
|
||||
user: ['Build your own ChatGPT Plugin!'],
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* components:
|
||||
* schemas:
|
||||
* getTodosResponse:
|
||||
* type: object
|
||||
* properties:
|
||||
* todos:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* description: The list of todos.
|
||||
*/
|
||||
|
||||
const router = new Router()
|
||||
router
|
||||
.get('/chatgpt-plugin', (ctx) => {
|
||||
ctx.response.body = 'Building ChatGPT plugins with Deno!'
|
||||
})
|
||||
/**
|
||||
* @openapi
|
||||
* /chatgpt-plugin/todos/{username}:
|
||||
* get:
|
||||
* operationId: getTodos
|
||||
* summary: Get the list of todos
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: username
|
||||
* schema:
|
||||
* type: string
|
||||
* required: true
|
||||
* description: The name of the user.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/getTodosResponse'
|
||||
*/
|
||||
.get('/chatgpt-plugin/todos/:username', (ctx) => {
|
||||
const username = ctx.params.username.toLowerCase()
|
||||
ctx.response.body = _TODOS[username] ?? []
|
||||
})
|
||||
.get('/chatgpt-plugin/openapi.json', (ctx) => {
|
||||
ctx.response.body = JSON.stringify(openapi)
|
||||
ctx.response.headers.set('Content-Type', 'application/json')
|
||||
})
|
||||
|
||||
const app = new Application()
|
||||
app.use(router.routes())
|
||||
app.use(router.allowedMethods())
|
||||
|
||||
await app.listen({ port: 8000 })
|
||||
```
|
||||
|
||||
With our JSDoc annotation in place, we can now run the generation script in the terminal:
|
||||
|
||||
```bash
|
||||
deno run --allow-read --allow-write scripts/generate-openapi-spec.ts
|
||||
```
|
||||
|
||||
## Adding the CORS headers
|
||||
|
||||
Lastly, we need to add some CORS headers to make the browser happy. We define them in a `/functions/_shared/cors.ts` [file](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/functions/_shared/cors.ts) so we can easily reuse them across our `main` and `chatgpt-plugins` function.
|
||||
|
||||
```tsx
|
||||
// /functions/_shared/cors.ts
|
||||
export const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': 'https://chat.openai.com',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Allow-Private-Network': 'true',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
}
|
||||
```
|
||||
|
||||
Now we can easily add them to all our `chatgpt-plugin` routes a middleware for our oak application.
|
||||
|
||||
```ts
|
||||
// /functions/chatgpt-plugin/index.ts
|
||||
import { Application, Router } from 'https://deno.land/x/oak@v11.1.0/mod.ts'
|
||||
import { corsHeaders } from '../_shared/cors.ts'
|
||||
|
||||
// [...]
|
||||
const app = new Application()
|
||||
// ChatGPT specific CORS headers
|
||||
app.use(async (ctx, next) => {
|
||||
await next()
|
||||
let key: keyof typeof corsHeaders
|
||||
for (key in corsHeaders) {
|
||||
ctx.response.headers.set(key, corsHeaders[key])
|
||||
}
|
||||
})
|
||||
app.use(router.routes())
|
||||
app.use(router.allowedMethods())
|
||||
|
||||
await app.listen({ port: 8000 })
|
||||
```
|
||||
|
||||
## Running locally with Docker
|
||||
|
||||
Now that we’ve got all the pieces in place, let’s spin up Edge Runtime locally and test things out. For this, we need a [Dockerfile](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/Dockerfile) and for convenience, we can add a [docker-compose file](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/docker-compose.yml) also.
|
||||
|
||||
```txt
|
||||
// Dockerfile
|
||||
FROM ghcr.io/supabase/edge-runtime:v1.2.18
|
||||
|
||||
COPY ./functions /home/deno/functions
|
||||
CMD [ "start", "--main-service", "/home/deno/functions/main" ]
|
||||
```
|
||||
|
||||
This will pull down Edge Runtime v1.2.18 (you can check the l[atest release here](https://github.com/supabase/edge-runtime/releases)) and start up the main service (our `/functions/main/index.ts` function).
|
||||
|
||||
```yaml
|
||||
// docker-compose.yml
|
||||
version: "3.9"
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./functions
|
||||
target: /home/deno/functions
|
||||
ports:
|
||||
- "8000:9000"
|
||||
```
|
||||
|
||||
Edge Runtime will serve requests on port `9000`, so we’re creating a mapping from `[localhost:8000](http://localhost:8000)` where we want to serve our requests locally (of course you can adapt this to your needs) to port `9000` of our Docker container.
|
||||
|
||||
Furthermore, we’re using [bind mounts](https://docs.docker.com/storage/bind-mounts/) to mount our functions directory into the container. This allows us to make modifications to our functions without needing to rebuild the container after, making for a great local developer experience.
|
||||
|
||||
That’s it, now we can build and spin up our container from the terminal:
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
Go ahead and try it out by visiting:
|
||||
|
||||
- [http://localhost:8000/chatgpt-plugin](http://localhost:8000/chatgpt-plugin)
|
||||
- [http://localhost:8000/.well-known/ai-plugin.json](http://localhost:8000/.well-known/ai-plugin.json)
|
||||
- [http://localhost:8000/chatgpt-plugin/openapi.json](http://localhost:8000/chatgpt-plugin/openapi.json)
|
||||
- [http://localhost:8000/chatgpt-plugin/todos/user](http://localhost:8000/chatgpt-plugin/todos/user)
|
||||
|
||||
## Installing and testing the plugin locally
|
||||
|
||||
You can conveniently test your plugin while running it on [localhost](http://localhost) using the [ChatGPT UI](https://chat.openai.com/):
|
||||
|
||||
1. Select the plugin model from the top drop down, then select “Plugins”, “Plugin Store”, and finally “Develop your own plugin”.
|
||||
2. Enter `localhost:8000` and click "Find manifest file".
|
||||
3. Confirm with “Install [localhost](http://localhost) plugin”.
|
||||
|
||||
That’s it, now go ahead and ask some questions, e.g. you can start with “Do I have any todos?”
|
||||
|
||||

|
||||
|
||||
There you are, now go ahead and build your own plugin as it says on your todo list ;)
|
||||
|
||||
## Deploying to Fly.io
|
||||
|
||||
Once you’re happy with the functionality of your plugin, go ahead and deploy it to [Fly.io](http://Fly.io). After installing the [flyctl cli](https://fly.io/docs/hands-on/install-flyctl/), it only takes a couple of steps:
|
||||
|
||||
- Change `http://localhost:8000` to your Fly domain in the `/main/ai-plugins.json` file
|
||||
- Open `fly.toml` and update the app name and optionally the region etc.
|
||||
- In your terminal, run `fly apps create` and specify the app name you just set in your `fly.toml` file.
|
||||
- Finally, run `fly deploy`.
|
||||
|
||||
There you go, now you’re ready to [release your plugin to the world](https://platform.openai.com/docs/plugins/production/faq) \o/
|
||||
|
||||
## Conclusion
|
||||
|
||||
ChatGPT is a powerful new interface and its usage is growing rapidly. With ChatGPT Plugins you can allow your users to access your service directly from ChatGPT, using cutting edge technologies like TypeScript and Deno.
|
||||
|
||||
In a next step you can add authentication to your plugin, let us know on [Twitter](https://twitter.com/Supabase) if you’d be interested in a tutorial for that. We can’t wait to see what you will build!
|
||||
|
||||
## More AI resources
|
||||
|
||||
- [OpenAI ChatGPT Plugin docs](https://platform.openai.com/docs/plugins/introduction)
|
||||
- [Advanced plugin template](https://github.com/openai/chatgpt-retrieval-plugin)
|
||||
- Learn to build your own ChatGPT-style docs search with [Deno Fresh](https://deno.com/blog/build-chatgpt-doc-search-with-supabase-fresh) or [Next.js](https://vercel.com/templates/next.js/nextjs-openai-doc-search-starter)
|
||||
- [How we builtChatGPT for the Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs).
|
||||
- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { Button, Badge, Announcement, AnnouncementCountdown, IconStar, IconChevronDown } from 'ui'
|
||||
import { Button, Badge, IconStar, IconChevronDown } from 'ui'
|
||||
import FlyOut from '~/components/UI/FlyOut'
|
||||
import Transition from 'lib/Transition'
|
||||
|
||||
@@ -197,9 +197,8 @@ const Nav = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Announcement>
|
||||
<AnnouncementCountdown />
|
||||
</Announcement>
|
||||
{/* <Announcement>
|
||||
</Announcement> */}
|
||||
<div className="sticky top-0 z-40 transform" style={{ transform: 'translate3d(0,0,999px)' }}>
|
||||
<div
|
||||
className={[
|
||||
|
||||
@@ -129,6 +129,10 @@
|
||||
"text": "Acceptable Use Policy",
|
||||
"url": "/aup"
|
||||
},
|
||||
{
|
||||
"text": "Support Policy",
|
||||
"url": "/support-policy"
|
||||
},
|
||||
{
|
||||
"text": "Service Level Agreement",
|
||||
"url": "/sla"
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "10"
|
||||
"value": "60"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "50"
|
||||
"value": "200"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -49,12 +49,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "30"
|
||||
"value": "90"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "75"
|
||||
"value": "200"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -74,12 +74,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "50"
|
||||
"value": "120"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "150"
|
||||
"value": "200"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -99,7 +99,7 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "100"
|
||||
"value": "160"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
@@ -124,12 +124,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "200"
|
||||
"value": "240"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "600"
|
||||
"value": "700"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -149,12 +149,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "350"
|
||||
"value": "380"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "1200"
|
||||
"value": "1500"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -174,12 +174,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "420"
|
||||
"value": "480"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "2800"
|
||||
"value": "3000"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -199,12 +199,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "450"
|
||||
"value": "490"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "5600"
|
||||
"value": "6000"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -228,12 +228,12 @@
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "480"
|
||||
"value": "500"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "8600"
|
||||
"value": "9000"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -262,7 +262,7 @@
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "11,600"
|
||||
"value": "12,000"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
{
|
||||
"optimized_compute": {
|
||||
"title": "Optimized Compute",
|
||||
"features": [
|
||||
"For projects that need dedicated compute",
|
||||
"For scaling number of connections",
|
||||
"Additional CPU & RAM options",
|
||||
"Additional Disk IO Bandwidth"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Free (Included)" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$0" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (shared)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "1 GB" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "87 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "50"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Small" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$5" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (shared)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "2 GB" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "174 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "30"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "75"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Medium" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$50" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (shared)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "4 GB" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "347 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "150"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Large" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$100" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "8 GB" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "630 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "100"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "300"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$200" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "4-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "16 GB" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "1,188 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "200"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "2XL" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$400" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "8-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "32 GB" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "2,375 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "350"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "1200"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "4,750 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "420"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "2800"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "9,500 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "9,500 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "450"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "5600"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "maxDiskIO",
|
||||
"title": "Maximum Disk IO Bandwidth",
|
||||
"value": "14,250 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "14,250 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "480"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "8600"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "maxDiskIO",
|
||||
"title": "Maximum Disk IO Bandwidth",
|
||||
"value": "19,000 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "19,000 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "500"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "11,600"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Custom (need more?)" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "Contact Us" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "Contact Us" },
|
||||
{ "key": "memory", "title": "Memory", "value": "Contact Us" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "Contact Us" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "Contact Us"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "Contact Us"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "Contact Us"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"support": {
|
||||
"title": "Support",
|
||||
"features": [
|
||||
"Guaranteed Response Times",
|
||||
"SLAs",
|
||||
"Designated Support",
|
||||
"Advanced Support Scope"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Standard" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "Contact Us" },
|
||||
{
|
||||
"key": "severityLevelUrgent",
|
||||
"title": "1. Severity Level: Urgent",
|
||||
"value": "1 business hour (24/7x365)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelHigh",
|
||||
"title": "2. Severity Level: High",
|
||||
"value": "4 business hours (Monday - Friday)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelNormal",
|
||||
"title": "3. Severity Level: Normal",
|
||||
"value": "1 business day (Monday - Friday)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelLow",
|
||||
"title": "4. Severity Level: Low",
|
||||
"value": "2 business days (Monday - Friday)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Priority" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "Contact Us" },
|
||||
{
|
||||
"key": "severityLevelUrgent",
|
||||
"title": "1. Severity Level: Urgent",
|
||||
"value": "1 business hour (24/7x365)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelHigh",
|
||||
"title": "2. Severity Level: High",
|
||||
"value": "2 business hours (Monday - Friday)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelNormal",
|
||||
"title": "3. Severity Level: Normal",
|
||||
"value": "1 business day (Monday - Friday)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelLow",
|
||||
"title": "4. Severity Level: Low",
|
||||
"value": "2 business days (Monday - Friday)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{ "key": "plan", "title": "Plan", "value": "Priority Plus" },
|
||||
{ "key": "pricing", "title": "Pricing", "value": "Contact Us" },
|
||||
{
|
||||
"key": "severityLevelUrgent",
|
||||
"title": "1. Severity Level: Urgent",
|
||||
"value": "1 business hour (24/7x365)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelHigh",
|
||||
"title": "2. Severity Level: High",
|
||||
"value": "2 business hours (24/7x365)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelNormal",
|
||||
"title": "3. Severity Level: Normal",
|
||||
"value": "12 business hours (Monday - Friday)"
|
||||
},
|
||||
{
|
||||
"key": "severityLevelLow",
|
||||
"title": "4. Severity Level: Low",
|
||||
"value": "1 business days (Monday - Friday)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,12 @@
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Image from 'next/image'
|
||||
import Avatar from '~/components/Avatar'
|
||||
import CodeBlock from '~/components/CodeBlock/CodeBlock'
|
||||
import ImageGrid from '~/components/ImageGrid'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import Quote from '~/components/Quote'
|
||||
import mdxComponents from '~/lib/mdx/mdxComponents'
|
||||
|
||||
const ignoreClass = 'ignore-on-export'
|
||||
|
||||
export default function ContentsLayout({ context }: any) {
|
||||
export default function ContentsLayout({ context, meta }: any) {
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<NextSeo {...context.meta} />
|
||||
<NextSeo {...meta} />
|
||||
<MDXProvider components={mdxComponents()}>
|
||||
<div className="prose max-w-none">{context.children}</div>
|
||||
</MDXProvider>
|
||||
|
||||
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
|
||||
import SectionContainer from '../components/Layouts/SectionContainer'
|
||||
|
||||
export const meta = {
|
||||
title: 'Acceptable Use Policy',
|
||||
title: 'Acceptable Use Policy | Supabase',
|
||||
description: 'Supabase Acceptable Use Policy',
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
|
||||
import SectionContainer from '../components/Layouts/SectionContainer'
|
||||
|
||||
export const meta = {
|
||||
title: 'Privacy Policy',
|
||||
title: 'Privacy Policy | Supabase',
|
||||
description: 'Supabase Privacy Policy',
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
|
||||
import SectionContainer from '../components/Layouts/SectionContainer'
|
||||
|
||||
export const meta = {
|
||||
title: 'Service Level Agreement',
|
||||
title: 'Service Level Agreement | Supabase',
|
||||
description: 'Supabase Service Level Agreement',
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
|
||||
import SectionContainer from '../components/Layouts/SectionContainer'
|
||||
|
||||
export const meta = {
|
||||
title: 'SupaSquad',
|
||||
title: 'SupaSquad | Supabase',
|
||||
description: 'Supabase Advocate program',
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import Layout from '../layouts/Layout'
|
||||
import SectionContainer from '../components/Layouts/SectionContainer'
|
||||
|
||||
export const meta = {
|
||||
title: 'Support Policy',
|
||||
description: 'Supabase Support Policy',
|
||||
}
|
||||
|
||||
<SectionContainer>
|
||||
|
||||
# Support Policy
|
||||
|
||||
Supabase is dedicated to providing an exceptional customer experience. As a crucial aspect of
|
||||
this commitment, we offer limited technical support for all paid projects running on our hosted Supabase platform. Technical support is confined to the scope, hours, contacts, and channels below.
|
||||
|
||||
## Scope
|
||||
|
||||
Supabase's support offering is available only for the technologies supported by the Supabase Platform and is restricted to the following:
|
||||
|
||||
**Configuration Issues**
|
||||
|
||||
- Installation of extensions
|
||||
- Best practices for auth configuration
|
||||
- General questions about the supabase stack
|
||||
|
||||
**Troubleshooting**
|
||||
|
||||
- Providing workarounds or resolutions for known problems
|
||||
- Answering general how-to questions and providing pointers to documentation
|
||||
- Troubleshooting supported software packages (Realtime, Edge Functions, Webhooks, PostgREST, Auth) exhibiting erratic or faulty behavior on Supabase, independent of the user's application code
|
||||
|
||||
**Not Covered**
|
||||
|
||||
Supabase technical support services do not extend to the following areas:
|
||||
|
||||
- General debugging of user applications
|
||||
- Rewriting application code for compatibility with Supabase
|
||||
- Modifying and/or patching third-party or Open Source software packages for compatibility with Supabase
|
||||
|
||||
## Who can contact support?
|
||||
|
||||
Billing and account management support is available to all customers.
|
||||
|
||||
Limited technical support is accessible for Supabase customers utilizing paid Supabase resources and/or paid Supabase Add-Ons. Support requests will only be processed if:
|
||||
|
||||
- The request is made through one of our official support channels (see below)
|
||||
- The request originates from a registered Supabase account email address
|
||||
- The requester has Developer or higher access to any specific Supabase projects pertaining to the request.
|
||||
|
||||
## Official Support Channels
|
||||
|
||||
- Web and Dashboard: Support is available exclusively through the Supabase help feature in the dashboard or this website -[https://app.supabase.com/support/new](https://app.supabase.com/support/new)
|
||||
- Email: If for some reason you are unable to access the dashboard or ticket from you can contact support by emailing support@supabase.com from a registered Supabase account email address
|
||||
|
||||
## Community Support
|
||||
|
||||
Supabase support staff will only address support requests received through the official channels mentioned earlier. However, community channels may exist for peer-to-peer support and discussions. This is support provided by volunteers that contribute to the Supabase community.
|
||||
|
||||
For questions related to debugging code, we recommend reaching out on **[GitHub Discussions](https://github.com/supabase/supabase/discussions)** or on **[Discord](https://discord.supabase.com/)**. The community is made up of experienced developers who may be able to provide guidance and support with code-related issues.
|
||||
|
||||
To get the most helpful response from the community, providing precise and detailed information about your problem and any error messages you may be encountering is important. Be sure to **include any relevant code snippets explaining how to reproduce** the issue in your message.
|
||||
|
||||
Please note that Supabase team members may participate in community channels at their discretion, but there is no guarantee of response for support issues unless they are
|
||||
submitted through one of the official channels above.
|
||||
|
||||
## Service Level Agreement
|
||||
|
||||
Service Level Agreements are only available to Enterprise customers. You can details of our SLA including support response times on the dedicated page for this topic -
|
||||
[https://supabase.com/sla](https://supabase.com/sla)
|
||||
|
||||
## Proactive Monitoring
|
||||
|
||||
In the event of a platform issue, a notice will be posted on our platform status site at [status.supabase.com](https://status.supabase.com/) to promptly communicate the impact and status of any such issue. You do not need to submit a support ticket if there's an ongoing platform issue. Instead, monitor the status page which will always be kept up-to-date with the latest progress and information.
|
||||
|
||||
## Premium Support
|
||||
|
||||
Please contact us to find out about our Premium Support options available for Teams Tier and Enterprise customers using the form below:
|
||||
|
||||
[https://forms.supabase.com/enterprise](https://forms.supabase.com/enterprise)
|
||||
|
||||
</SectionContainer>
|
||||
|
||||
export default (context) => <Layout meta={meta} children={context.children} context={context} />
|
||||
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 656 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 4.5 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 448 KiB |
@@ -5,35 +5,35 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Angelico de los Reyes at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-angelico_de_los_reyes-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-point-in-time-recovery</guid>
|
||||
<title>Point in Time Recovery is now available for Pro projects</title>
|
||||
<link>https://supabase.com/blog/postgres-point-in-time-recovery</link>
|
||||
<description>We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use.</description>
|
||||
<pubDate>Thu, 15 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 15 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/continuous-postgresql-backup-walg</guid>
|
||||
<title>Continuous PostgreSQL Backups using WAL-G</title>
|
||||
<link>https://supabase.com/blog/continuous-postgresql-backup-walg</link>
|
||||
<description>Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G.</description>
|
||||
<pubDate>Sun, 02 Aug 2020 02:02:30 +0000</pubDate>
|
||||
<pubDate>Sat, 01 Aug 2020 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgresql-templates</guid>
|
||||
<title>What are PostgreSQL Templates?</title>
|
||||
<link>https://supabase.com/blog/postgresql-templates</link>
|
||||
<description>What are PostgreSQL templates and what are they used for?</description>
|
||||
<pubDate>Thu, 09 Jul 2020 02:02:30 +0000</pubDate>
|
||||
<pubDate>Wed, 08 Jul 2020 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgresql-physical-logical-backups</guid>
|
||||
<title>Physical vs Logical Backups in PostgreSQL</title>
|
||||
<link>https://supabase.com/blog/postgresql-physical-logical-backups</link>
|
||||
<description>What are physical and logical backups in Postgres?</description>
|
||||
<pubDate>Tue, 07 Jul 2020 02:02:30 +0000</pubDate>
|
||||
<pubDate>Mon, 06 Jul 2020 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Ant Wilson at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Sat, 27 Feb 2021 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Fri, 26 Feb 2021 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-ant_wilson-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/cracking-postgres-interview</guid>
|
||||
<title>Cracking PostgreSQL Interview Questions</title>
|
||||
<link>https://supabase.com/blog/cracking-postgres-interview</link>
|
||||
<description>Understand the top PostgreSQL Interview Questions</description>
|
||||
<pubDate>Sat, 27 Feb 2021 03:03:30 +0000</pubDate>
|
||||
<pubDate>Fri, 26 Feb 2021 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Bo Lu at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Wed, 14 Dec 2022 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Wed, 14 Dec 2022 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-bo_lu-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</guid>
|
||||
<title>Supabase Wrappers, a Postgres FDW framework written in Rust</title>
|
||||
<link>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</link>
|
||||
<description>A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more.</description>
|
||||
<pubDate>Wed, 14 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Wed, 14 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Mark Burggraf at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Wed, 23 Nov 2022 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Wed, 23 Nov 2022 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-burggraf-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/sql-or-nosql-both-with-postgresql</guid>
|
||||
<title>SQL or NoSQL? Why not use both (with PostgreSQL)?</title>
|
||||
<link>https://supabase.com/blog/sql-or-nosql-both-with-postgresql</link>
|
||||
<description>How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL</description>
|
||||
<pubDate>Wed, 23 Nov 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Wed, 23 Nov 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-wasm</guid>
|
||||
<title>Postgres WASM by Snaplet and Supabase</title>
|
||||
<link>https://supabase.com/blog/postgres-wasm</link>
|
||||
<description>We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet.</description>
|
||||
<pubDate>Sun, 02 Oct 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Sun, 02 Oct 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Greg Richardson at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Sun, 05 Feb 2023 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Sun, 05 Feb 2023 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-gregnr-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/openai-embeddings-postgres-vector</guid>
|
||||
<title>Storing OpenAI embeddings in Postgres with pgvector</title>
|
||||
<link>https://supabase.com/blog/openai-embeddings-postgres-vector</link>
|
||||
<description>An example of how to build an AI-powered search engine using OpenAI's embeddings and PostgreSQL.</description>
|
||||
<pubDate>Sun, 05 Feb 2023 03:03:30 +0000</pubDate>
|
||||
<pubDate>Sun, 05 Feb 2023 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Michel Pelletier at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-michel-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/vault-now-in-beta</guid>
|
||||
<title>Supabase Vault is now in Beta</title>
|
||||
<link>https://supabase.com/blog/vault-now-in-beta</link>
|
||||
<description>A Postgres extension to store encrypted secrets and encrypt data.</description>
|
||||
<pubDate>Thu, 15 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 15 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/transparent-column-encryption-with-postgres</guid>
|
||||
<title>Transparent Column Encryption with Postgres</title>
|
||||
<link>https://supabase.com/blog/transparent-column-encryption-with-postgres</link>
|
||||
<description>Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption.</description>
|
||||
<pubDate>Wed, 30 Nov 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Wed, 30 Nov 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,49 +5,49 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Oliver Rice at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Thu, 20 Apr 2023 02:02:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Thu, 20 Apr 2023 22:10:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-oli_rice-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/whats-new-in-pg-graphql-v1-2</guid>
|
||||
<title>What's New in pg_graphql v1.2</title>
|
||||
<link>https://supabase.com/blog/whats-new-in-pg-graphql-v1-2</link>
|
||||
<description>New Features in the v1.2 release of pg_graphql</description>
|
||||
<pubDate>Thu, 20 Apr 2023 02:02:30 +0000</pubDate>
|
||||
<pubDate>Thu, 20 Apr 2023 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/type-constraints-in-65-lines-of-sql</guid>
|
||||
<title>Type Constraints in 65 lines of SQL</title>
|
||||
<link>https://supabase.com/blog/type-constraints-in-65-lines-of-sql</link>
|
||||
<description>Creating validated data types in Postgres</description>
|
||||
<pubDate>Thu, 16 Feb 2023 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 16 Feb 2023 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/pg-graphql-v1</guid>
|
||||
<title>pg_graphql v1.0</title>
|
||||
<link>https://supabase.com/blog/pg-graphql-v1</link>
|
||||
<description>Announcing the v1.0 release of pg_graphql</description>
|
||||
<pubDate>Thu, 15 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 15 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</guid>
|
||||
<title>Supabase Wrappers, a Postgres FDW framework written in Rust</title>
|
||||
<link>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</link>
|
||||
<description>A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more.</description>
|
||||
<pubDate>Wed, 14 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Wed, 14 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation</guid>
|
||||
<title>pg_jsonschema: JSON Schema support for Postgres</title>
|
||||
<link>https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation</link>
|
||||
<description>Today we're releasing pg_jsonschema, a Postgres extension for JSON validation.</description>
|
||||
<pubDate>Thu, 18 Aug 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Thu, 18 Aug 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/audit</guid>
|
||||
<guid>https://supabase.com/blog/postgres-audit</guid>
|
||||
<title>Postgres Auditing in 150 lines of SQL</title>
|
||||
<link>https://supabase.com/blog/audit</link>
|
||||
<link>https://supabase.com/blog/postgres-audit</link>
|
||||
<description>PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL.</description>
|
||||
<pubDate>Mon, 07 Mar 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Mon, 07 Mar 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,49 +5,49 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Paul Copplestone at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Sun, 30 Apr 2023 02:02:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Sun, 30 Apr 2023 22:10:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-paul_copplestone-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-pluggable-strorage</guid>
|
||||
<title>Next steps for Postgres pluggable storage</title>
|
||||
<link>https://supabase.com/blog/postgres-pluggable-strorage</link>
|
||||
<description>Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core.</description>
|
||||
<pubDate>Sun, 30 Apr 2023 02:02:30 +0000</pubDate>
|
||||
<pubDate>Sun, 30 Apr 2023 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</guid>
|
||||
<title>Supabase Wrappers, a Postgres FDW framework written in Rust</title>
|
||||
<link>https://supabase.com/blog/postgres-foreign-data-wrappers-rust</link>
|
||||
<description>A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more.</description>
|
||||
<pubDate>Wed, 14 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Wed, 14 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-crdt</guid>
|
||||
<title>pg_crdt - an experimental CRDT extension for Postgres</title>
|
||||
<link>https://supabase.com/blog/postgres-crdt</link>
|
||||
<description>Embedding Yjs and Automerge into Postgres for collaborative applications.</description>
|
||||
<pubDate>Fri, 09 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Fri, 09 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/partial-postgresql-data-dumps-with-rls</guid>
|
||||
<title>Partial data dumps using Postgres Row Level Security</title>
|
||||
<link>https://supabase.com/blog/partial-postgresql-data-dumps-with-rls</link>
|
||||
<description>Using RLS to create seed files for local PostgreSQL testing.</description>
|
||||
<pubDate>Mon, 27 Jun 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Mon, 27 Jun 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-as-a-cron-server</guid>
|
||||
<title>Postgres as a CRON Server</title>
|
||||
<link>https://supabase.com/blog/postgres-as-a-cron-server</link>
|
||||
<description>Running repetitive tasks with your Postgres database.</description>
|
||||
<pubDate>Fri, 05 Mar 2021 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 04 Mar 2021 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgresql-views</guid>
|
||||
<title>Postgres Views</title>
|
||||
<link>https://supabase.com/blog/postgresql-views</link>
|
||||
<description>Creating and using a view in PostgreSQL.</description>
|
||||
<pubDate>Wed, 18 Nov 2020 03:03:30 +0000</pubDate>
|
||||
<pubDate>Tue, 17 Nov 2020 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Pavel Borisov at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-pavel-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/new-in-postgres-15</guid>
|
||||
<title>What's new in Postgres 15?</title>
|
||||
<link>https://supabase.com/blog/new-in-postgres-15</link>
|
||||
<description>Describes the release of Postgres 15, new features and reasons to use it</description>
|
||||
<pubDate>Thu, 15 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 15 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgresql-commitfest</guid>
|
||||
<title>What is PostgreSQL commitfest and how to contribute</title>
|
||||
<link>https://supabase.com/blog/postgresql-commitfest</link>
|
||||
<description>A time-tested method for contributing to the core Postgres code</description>
|
||||
<pubDate>Wed, 26 Oct 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Wed, 26 Oct 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Steve Chavez at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 03:03:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Thu, 15 Dec 2022 23:11:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-steve_chavez-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgrest-11-prerelease</guid>
|
||||
<title>PostgREST 11 pre-release</title>
|
||||
<link>https://supabase.com/blog/postgrest-11-prerelease</link>
|
||||
<description>Describes new features of PostgREST 11 pre-release</description>
|
||||
<pubDate>Thu, 15 Dec 2022 03:03:30 +0000</pubDate>
|
||||
<pubDate>Thu, 15 Dec 2022 23:11:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/roles-postgres-hooks</guid>
|
||||
<title>Protecting reserved roles with PostgreSQL Hooks</title>
|
||||
<link>https://supabase.com/blog/roles-postgres-hooks</link>
|
||||
<description>Using Postgres Hooks to protect functionality in your Postgres database.</description>
|
||||
<pubDate>Thu, 01 Jul 2021 02:02:30 +0000</pubDate>
|
||||
<pubDate>Thu, 01 Jul 2021 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -5,28 +5,28 @@
|
||||
<link>https://supabase.com/blog</link>
|
||||
<description>Latest Postgres news from Victor at Supabase</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>Thu, 13 Oct 2022 02:02:30 +0000</lastBuildDate>
|
||||
<lastBuildDate>Thu, 13 Oct 2022 22:10:00 +0000</lastBuildDate>
|
||||
<atom:link href="https://supabase.com/planetpg-victor-rss.xml" rel="self" type="application/rss+xml"/>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/postgres-full-text-search-vs-the-rest</guid>
|
||||
<title>Postgres Full Text Search vs the rest</title>
|
||||
<link>https://supabase.com/blog/postgres-full-text-search-vs-the-rest</link>
|
||||
<description>Comparing one of the most popular Postgres features against alternatives</description>
|
||||
<pubDate>Thu, 13 Oct 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Thu, 13 Oct 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/choosing-a-postgres-primary-key</guid>
|
||||
<title>Choosing a Postgres Primary Key</title>
|
||||
<link>https://supabase.com/blog/choosing-a-postgres-primary-key</link>
|
||||
<description>Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out</description>
|
||||
<pubDate>Wed, 07 Sep 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Wed, 07 Sep 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<guid>https://supabase.com/blog/seen-by-in-postgresql</guid>
|
||||
<title>Implementing "seen by" functionality with Postgres</title>
|
||||
<link>https://supabase.com/blog/seen-by-in-postgresql</link>
|
||||
<description>Different approaches for tracking visitor counts with PostgreSQL.</description>
|
||||
<pubDate>Sun, 17 Jul 2022 02:02:30 +0000</pubDate>
|
||||
<pubDate>Sun, 17 Jul 2022 22:10:00 +0000</pubDate>
|
||||
</item>
|
||||
|
||||
</channel>
|
||||
|
||||
@@ -7,6 +7,15 @@ import type { Database } from "./lib/database.types";
|
||||
export async function middleware(req: NextRequest) {
|
||||
const res = NextResponse.next();
|
||||
const supabase = createMiddlewareSupabaseClient<Database>({ req, res });
|
||||
await supabase.auth.getSession();
|
||||
|
||||
const { data: { session } } = await supabase.auth.getSession(); // destructure the data object to obtain the session object
|
||||
|
||||
if (session === null) return NextResponse.redirect(new URL("/login", req.nextUrl));
|
||||
// instead of "/login" you can redirect to any other route in case the user is not logged in
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [], // add the routes you wish the middleware to run in. You can also use regex
|
||||
};
|
||||
@@ -55,6 +55,7 @@
|
||||
"clsx": "^1.2.1",
|
||||
"common": "*",
|
||||
"config": "*",
|
||||
"framer-motion": "^6.5.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"isbot": "^3.6.5",
|
||||
@@ -51546,6 +51547,7 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"ejs": "^3.1.8",
|
||||
"eslint": "8.9.0",
|
||||
"framer-motion": "^6.5.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"globby": "^12.0.2",
|
||||
"gray-matter": "^4.0.3",
|
||||
@@ -70213,4 +70215,4 @@
|
||||
"integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ export * from './src/components/Icon'
|
||||
export * from './src/components/Icon/IconContext'
|
||||
export * from './src/components/GlassPanel'
|
||||
export * from './src/components/IconPanel'
|
||||
// export * from './src/components/Image'
|
||||
export * from './src/components/Icon/IconBackground'
|
||||
|
||||
// DISPLAYS
|
||||
|
||||
@@ -30,6 +30,7 @@ export * from './src/components/CodeBlock'
|
||||
// NAV
|
||||
|
||||
export * from './src/components/Menu'
|
||||
export * from './src/components/TextLink'
|
||||
|
||||
// OVERLAYS
|
||||
|
||||
|
||||
@@ -382,6 +382,9 @@ const AiCommand = () => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Detect an IME composition (so that we can ignore Enter keypress)
|
||||
const [isImeComposing, setIsImeComposing] = useState(false)
|
||||
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<div className={cn('relative mb-[145px] py-4 max-h-[720px] overflow-auto')}>
|
||||
@@ -478,7 +481,7 @@ const AiCommand = () => {
|
||||
<div className="absolute bottom-0 w-full bg-scale-200 py-3">
|
||||
{messages.length > 0 && !hasError && <AiWarning className="mb-3 mx-3" />}
|
||||
<Input
|
||||
className="bg-scale-100 rounded mx-3"
|
||||
className="bg-scale-100 rounded mx-3 [&_input]:pr-32 md:[&_input]:pr-40"
|
||||
inputRef={inputRef}
|
||||
autoFocus
|
||||
placeholder={
|
||||
@@ -506,17 +509,15 @@ const AiCommand = () => {
|
||||
setSearch(e.target.value)
|
||||
}
|
||||
}}
|
||||
onCompositionStart={() => setIsImeComposing(true)}
|
||||
onCompositionEnd={() => setIsImeComposing(false)}
|
||||
onKeyDown={(e) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
if (!search) {
|
||||
if (!search || isLoading || isResponding || isImeComposing) {
|
||||
return
|
||||
}
|
||||
if (isLoading || isResponding) {
|
||||
return
|
||||
}
|
||||
handleSubmit(search)
|
||||
return
|
||||
return handleSubmit(search)
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
@@ -6,17 +6,25 @@ interface AiIconProps {
|
||||
|
||||
export const AiIcon = ({ className = 'text-brand-900' }: AiIconProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`w-6 h-6 ${className}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className={`w-6 h-6 ${className}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
|
||||
stroke="#4CC38A"
|
||||
strokeMiterlimit="10"
|
||||
d="m10.618 11.832-2.283 2.657a.5.5 0 0 1-.88-.325v-3.98H2.728a1 1 0 0 1-.777-1.628l5.402-6.682a.5.5 0 0 1 .889.314v3.915h5.043a1 1 0 0 1 .766 1.642l-1.373 1.638"
|
||||
/>
|
||||
<path
|
||||
stroke="#4CC38A"
|
||||
strokeLinejoin="bevel"
|
||||
strokeMiterlimit="10"
|
||||
d="m12.232 14.15.418-1.254a2.383 2.383 0 0 1 1.507-1.507l1.253-.417-1.253-.418a2.383 2.383 0 0 1-1.508-1.507l-.417-1.253-.417 1.253a2.383 2.383 0 0 1-1.507 1.507l-1.253.418 1.253.417a2.383 2.383 0 0 1 1.507 1.507l.417 1.253Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -241,7 +241,7 @@ export const CommandItem = React.forwardRef<CommandPrimitiveItemElement, Command
|
||||
{...props}
|
||||
>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row items-center">{children}</div>
|
||||
<div className="flex flex-row flex-grow items-center">{children}</div>
|
||||
{badge}
|
||||
</div>
|
||||
</CommandPrimitive.Item>
|
||||
|
||||
@@ -74,6 +74,9 @@ const GenerateSQL = () => {
|
||||
if (search) handleSubmit(search)
|
||||
}, [])
|
||||
|
||||
// Detect an IME composition (so that we can ignore Enter keypress)
|
||||
const [isImeComposing, setIsImeComposing] = useState(false)
|
||||
|
||||
const formatAnswer = (answer: string) => {
|
||||
try {
|
||||
return format(answer, {
|
||||
@@ -210,7 +213,9 @@ const GenerateSQL = () => {
|
||||
onKeyDown={(e) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
if (!search || isLoading || isResponding) return
|
||||
if (!search || isLoading || isResponding || isImeComposing) {
|
||||
return
|
||||
}
|
||||
return handleSubmit(query)
|
||||
default:
|
||||
return
|
||||
@@ -311,10 +316,14 @@ const GenerateSQL = () => {
|
||||
setSearch(e.target.value)
|
||||
}
|
||||
}}
|
||||
onCompositionStart={() => setIsImeComposing(true)}
|
||||
onCompositionEnd={() => setIsImeComposing(false)}
|
||||
onKeyDown={(e) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
if (!search || isLoading || isResponding) return
|
||||
if (!search || isLoading || isResponding || isImeComposing) {
|
||||
return
|
||||
}
|
||||
return handleSubmit(search)
|
||||
default:
|
||||
return
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export const IconBackground: React.FC = (props) => (
|
||||
<div className="shrink-0 bg-green-600 w-8 h-8 flex items-center justify-center rounded">
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
@@ -1,11 +1,14 @@
|
||||
import { useTheme } from 'common/Providers'
|
||||
import * as React from 'react'
|
||||
import { ChevronRight } from 'react-feather'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
title?: string
|
||||
tooltip?: string
|
||||
|
||||
icon?: string | React.ReactNode
|
||||
iconSize?: 'sm' | 'lg'
|
||||
children?: React.ReactNode
|
||||
|
||||
background?: boolean
|
||||
@@ -13,15 +16,19 @@ interface Props {
|
||||
hasLightIcon?: boolean
|
||||
|
||||
showLink?: boolean
|
||||
hideArrow?: boolean
|
||||
}
|
||||
|
||||
const IconPanel = ({
|
||||
title,
|
||||
tooltip,
|
||||
icon,
|
||||
iconSize = 'sm',
|
||||
children,
|
||||
background = true,
|
||||
hasLightIcon,
|
||||
showLink = false,
|
||||
hideArrow = false,
|
||||
}: Props) => {
|
||||
const { isDarkMode } = useTheme()
|
||||
|
||||
@@ -31,7 +38,7 @@ const IconPanel = ({
|
||||
className={[
|
||||
'relative',
|
||||
'flex items-center justify-center shrink-0',
|
||||
'h-10 w-10 rounded-lg',
|
||||
iconSize === 'lg' ? 'h-16 w-16 rounded-lg' : 'h-10 w-10 rounded-lg',
|
||||
'group',
|
||||
'cursor-pointer',
|
||||
'overflow-hidden',
|
||||
@@ -48,44 +55,62 @@ const IconPanel = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={['relative', 'group'].join(' ')}>
|
||||
<div className={['peer relative', 'flex flex-col', icon ? 'gap-6' : 'gap-2'].join(' ')}>
|
||||
<div className={['flex gap-3', children ? 'items-start' : 'items-center'].join(' ')}>
|
||||
{typeof icon === 'string' ? (
|
||||
<IconContainer>
|
||||
<img
|
||||
className="w-5"
|
||||
src={`${icon}${hasLightIcon && !isDarkMode ? '-light' : ''}.svg`}
|
||||
/>
|
||||
</IconContainer>
|
||||
) : (
|
||||
<IconContainer>{icon}</IconContainer>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<h5 className="text-base text-scale-1200 m-0">{title}</h5>
|
||||
<div
|
||||
className="
|
||||
<>
|
||||
<div className={['relative', 'group'].join(' ')} data-tip={tooltip}>
|
||||
<div className={['peer relative', 'flex flex-col', icon ? 'gap-6' : 'gap-2'].join(' ')}>
|
||||
<div
|
||||
className={[
|
||||
'flex',
|
||||
children ? 'items-start' : 'items-center',
|
||||
(title || !hideArrow || showLink) && 'gap-3',
|
||||
].join(' ')}
|
||||
>
|
||||
{typeof icon === 'string' ? (
|
||||
<IconContainer>
|
||||
<img
|
||||
className={iconSize === 'lg' ? 'w-8' : 'w-5'}
|
||||
src={`${icon}${hasLightIcon && !isDarkMode ? '-light' : ''}.svg`}
|
||||
/>
|
||||
</IconContainer>
|
||||
) : (
|
||||
<IconContainer>{icon}</IconContainer>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-3">
|
||||
{title && <h5 className="text-base text-scale-1200 m-0">{title}</h5>}
|
||||
{!hideArrow && (
|
||||
<div
|
||||
className="
|
||||
transition-all ease-out -ml-1 opacity-0
|
||||
text-scale-800
|
||||
group-hover:opacity-100
|
||||
group-hover:ml-0"
|
||||
>
|
||||
<ChevronRight strokeWidth={2} size={14} />
|
||||
>
|
||||
<ChevronRight strokeWidth={2} size={14} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{children && <span className="text-sm text-scale-1100 not-prose">{children}</span>}
|
||||
{showLink && <span className="text-brand-900 justify-end text-sm">Learn more</span>}
|
||||
</div>
|
||||
{children && <span className="text-sm text-scale-1100 not-prose">{children}</span>}
|
||||
{showLink && <span className="text-brand-900 justify-end text-sm">Learn more</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="
|
||||
<div
|
||||
className="
|
||||
absolute transition-all ease-in
|
||||
-z-10 -inset-3 rounded-2xl
|
||||
bg-scale-200 dark:bg-whiteA-300 opacity-0 peer-hover:opacity-100"
|
||||
></div>
|
||||
</div>
|
||||
></div>
|
||||
</div>
|
||||
{tooltip && (
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
backgroundColor="var(--colors-scale1)"
|
||||
textColor="var(--colors-scale11)"
|
||||
className="!py-2 !px-4"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ import { HIDDEN_PLACEHOLDER } from './../../lib/constants'
|
||||
import styleHandler from '../../lib/theme/styleHandler'
|
||||
import { useFormContext } from '../Form/FormContext'
|
||||
|
||||
export interface Props extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
||||
export interface Props
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'onCopy'> {
|
||||
copy?: boolean
|
||||
onCopy?: () => void
|
||||
defaultValue?: string | number
|
||||
descriptionText?: string | React.ReactNode | undefined
|
||||
disabled?: boolean
|
||||
@@ -52,6 +54,7 @@ function Input({
|
||||
layout,
|
||||
onChange,
|
||||
onBlur,
|
||||
onCopy,
|
||||
placeholder,
|
||||
type = 'text',
|
||||
value = undefined,
|
||||
@@ -100,7 +103,7 @@ function Input({
|
||||
// error = touched && touched[id] ? error : undefined
|
||||
// }, [errors, touched])
|
||||
|
||||
function onCopy(value: any) {
|
||||
function _onCopy(value: any) {
|
||||
navigator.clipboard.writeText(value)?.then(
|
||||
function () {
|
||||
/* clipboard successfully set */
|
||||
@@ -108,6 +111,7 @@ function Input({
|
||||
setTimeout(function () {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
},
|
||||
function () {
|
||||
/* clipboard write failed */
|
||||
@@ -152,6 +156,7 @@ function Input({
|
||||
name={name}
|
||||
onChange={onInputChange}
|
||||
onBlur={handleBlurEvent}
|
||||
onCopy={onCopy}
|
||||
placeholder={placeholder}
|
||||
ref={inputRef}
|
||||
type={type}
|
||||
@@ -164,7 +169,7 @@ function Input({
|
||||
<div className={__styles.actions_container}>
|
||||
{error && <InputErrorIcon size={size} />}
|
||||
{copy && !(reveal && hidden) ? (
|
||||
<Button size="tiny" type="default" icon={<IconCopy />} onClick={() => onCopy(value)}>
|
||||
<Button size="tiny" type="default" icon={<IconCopy />} onClick={() => _onCopy(value)}>
|
||||
{copyLabel}
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -182,7 +187,7 @@ function Input({
|
||||
}
|
||||
|
||||
export interface TextAreaProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLTextAreaElement>, 'size'> {
|
||||
extends Omit<React.InputHTMLAttributes<HTMLTextAreaElement>, 'size' | 'onCopy'> {
|
||||
descriptionText?: string
|
||||
error?: string
|
||||
icon?: any
|
||||
@@ -197,6 +202,7 @@ export interface TextAreaProps
|
||||
borderless?: boolean
|
||||
validation?: (x: any) => void
|
||||
copy?: boolean
|
||||
onCopy?: () => void
|
||||
actions?: React.ReactNode
|
||||
}
|
||||
|
||||
@@ -224,13 +230,14 @@ function TextArea({
|
||||
borderless = false,
|
||||
validation,
|
||||
copy = false,
|
||||
onCopy,
|
||||
actions,
|
||||
...props
|
||||
}: TextAreaProps) {
|
||||
const [charLength, setCharLength] = useState(0)
|
||||
const [copyLabel, setCopyLabel] = useState('Copy')
|
||||
|
||||
function onCopy(value: any) {
|
||||
function _onCopy(value: any) {
|
||||
navigator.clipboard.writeText(value).then(
|
||||
function () {
|
||||
/* clipboard successfully set */
|
||||
@@ -238,6 +245,7 @@ function TextArea({
|
||||
setTimeout(function () {
|
||||
setCopyLabel('Copy')
|
||||
}, 3000)
|
||||
onCopy?.()
|
||||
},
|
||||
function () {
|
||||
/* clipboard write failed */
|
||||
@@ -308,6 +316,7 @@ function TextArea({
|
||||
placeholder={placeholder}
|
||||
onChange={onInputChange}
|
||||
onBlur={handleBlurEvent}
|
||||
onCopy={onCopy}
|
||||
value={value}
|
||||
className={classes.join(' ')}
|
||||
maxLength={limit}
|
||||
@@ -323,7 +332,7 @@ function TextArea({
|
||||
<Button
|
||||
size="tiny"
|
||||
type="default"
|
||||
onClick={() => onCopy(value)}
|
||||
onClick={() => _onCopy(value)}
|
||||
icon={<IconCopy />}
|
||||
>
|
||||
{copyLabel}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { IconChevronRight } from 'ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
function TextLink({ url = '', label, ...props }: any) {
|
||||
return (
|
||||
<Link href={url} passHref>
|
||||
<a
|
||||
className="text-scale-1100 hover:text-scale-1200 mt-3 block cursor-pointer text-sm"
|
||||
{...props}
|
||||
>
|
||||
<div className="group flex items-center gap-1">
|
||||
<span className="sr-only">{`${label} about ${url}`}</span>
|
||||
<span>{label}</span>
|
||||
<div className="transition-all group-hover:ml-0.5">
|
||||
<IconChevronRight size={14} strokeWidth={2} />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextLink
|
||||
@@ -0,0 +1 @@
|
||||
export { default as TextLink } from './TextLink'
|
||||
@@ -2636,13 +2636,15 @@ functions:
|
||||
```kotlin
|
||||
val client = createSupabaseClient(
|
||||
supabaseUrl = "https://id.supabase.co",
|
||||
supabaseKey = "service_role"
|
||||
supabaseKey = "supabaseKey"
|
||||
) {
|
||||
install(GoTrue) {
|
||||
autoLoadFromStorage = false
|
||||
alwaysAutoRefresh = false
|
||||
}
|
||||
// install other plugins (these will use the service role key)
|
||||
}
|
||||
client.gotrue.importAuthToken("service_role")
|
||||
|
||||
// Access auth admin api
|
||||
val adminGoTrueClient = client.gotrue.admin
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import Editor, { Monaco, OnMount } from '@monaco-editor/react'
|
||||
import { timeout } from 'lib/helpers'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { MutableRefObject, useEffect, useRef } from 'react'
|
||||
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
|
||||
export type IStandaloneCodeEditor = Parameters<OnMount>[0]
|
||||
|
||||
export type MonacoEditorProps = {
|
||||
id: string
|
||||
editorRef: MutableRefObject<IStandaloneCodeEditor | null>
|
||||
isExecuting: boolean
|
||||
executeQuery: (overrideSql?: string) => void
|
||||
executeQuery: () => void
|
||||
}
|
||||
|
||||
const MonacoEditor = ({ id, isExecuting, executeQuery }: MonacoEditorProps) => {
|
||||
const MonacoEditor = ({ id, editorRef, isExecuting, executeQuery }: MonacoEditorProps) => {
|
||||
const snap = useSqlEditorStateSnapshot({ sync: true })
|
||||
const snippet = snap.snippets[id]
|
||||
|
||||
const editorRef = useRef<any>(null)
|
||||
const monacoRef = useRef<Monaco | null>(null)
|
||||
const executeQueryRef = useRef(executeQuery)
|
||||
executeQueryRef.current = executeQuery
|
||||
@@ -22,7 +24,9 @@ const MonacoEditor = ({ id, isExecuting, executeQuery }: MonacoEditorProps) => {
|
||||
if (!editorRef.current || !monacoRef.current) return
|
||||
|
||||
const model = editorRef.current.getModel()
|
||||
monacoRef.current.editor.setModelMarkers(model, 'owner', [])
|
||||
if (model !== null) {
|
||||
monacoRef.current.editor.setModelMarkers(model, 'owner', [])
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -49,13 +53,7 @@ const MonacoEditor = ({ id, isExecuting, executeQuery }: MonacoEditorProps) => {
|
||||
contextMenuGroupId: 'operation',
|
||||
contextMenuOrder: 0,
|
||||
run: () => {
|
||||
if (isExecuting) return
|
||||
|
||||
const selectedValue = (editorRef?.current)
|
||||
.getModel()
|
||||
.getValueInRange(editorRef?.current?.getSelection())
|
||||
|
||||
executeQueryRef.current?.(selectedValue || editorRef?.current?.getValue())
|
||||
executeQueryRef.current()
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useParams } from 'common'
|
||||
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import Split from 'react-split'
|
||||
import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor'
|
||||
import useLatest from 'hooks/misc/useLatest'
|
||||
import MonacoEditor from './MonacoEditor'
|
||||
import UtilityPanel from './UtilityPanel/UtilityPanel'
|
||||
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
|
||||
|
||||
export interface SQLEditorProps {}
|
||||
import useLatest from 'hooks/misc/useLatest'
|
||||
import MonacoEditor, { IStandaloneCodeEditor } from './MonacoEditor'
|
||||
import UtilityPanel from './UtilityPanel/UtilityPanel'
|
||||
|
||||
const SQLEditor = () => {
|
||||
const { ref, id } = useParams()
|
||||
@@ -38,22 +36,26 @@ const SQLEditor = () => {
|
||||
if (id) snap.setSplitSizes(id, sizes)
|
||||
}, [])
|
||||
|
||||
const executeQuery = useCallback(
|
||||
(overrideSql?: string) => {
|
||||
// use the latest state
|
||||
const state = getSqlEditorStateSnapshot()
|
||||
const snippet = idRef.current && state.snippets[idRef.current]
|
||||
const editorRef = useRef<IStandaloneCodeEditor | null>(null)
|
||||
|
||||
if (project && snippet && !isExecuting) {
|
||||
execute?.({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
sql: overrideSql ?? snippet.snippet.content.sql,
|
||||
})
|
||||
}
|
||||
},
|
||||
[isExecuting, project]
|
||||
)
|
||||
const executeQuery = useCallback(() => {
|
||||
// use the latest state
|
||||
const state = getSqlEditorStateSnapshot()
|
||||
const snippet = idRef.current && state.snippets[idRef.current]
|
||||
|
||||
if (project && snippet && !isExecuting && editorRef.current !== null) {
|
||||
const editor = editorRef.current
|
||||
const selection = editor.getSelection()
|
||||
const selectedValue = selection ? editor.getModel()?.getValueInRange(selection) : undefined
|
||||
const overrideSql = selectedValue || editorRef.current?.getValue()
|
||||
|
||||
execute({
|
||||
projectRef: project.ref,
|
||||
connectionString: project.connectionString,
|
||||
sql: overrideSql ?? snippet.snippet.content.sql,
|
||||
})
|
||||
}
|
||||
}, [isExecuting, project])
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
@@ -72,7 +74,12 @@ const SQLEditor = () => {
|
||||
{isLoading ? (
|
||||
<div className="flex h-full w-full items-center justify-center">Loading...</div>
|
||||
) : (
|
||||
<MonacoEditor id={id!} isExecuting={isExecuting} executeQuery={executeQuery} />
|
||||
<MonacoEditor
|
||||
id={id}
|
||||
editorRef={editorRef}
|
||||
isExecuting={isExecuting}
|
||||
executeQuery={executeQuery}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -80,7 +87,7 @@ const SQLEditor = () => {
|
||||
{isLoading ? (
|
||||
<div className="flex h-full w-full items-center justify-center">Loading...</div>
|
||||
) : (
|
||||
<UtilityPanel id={id!} isExecuting={isExecuting} executeQuery={executeQuery} />
|
||||
<UtilityPanel id={id} isExecuting={isExecuting} executeQuery={executeQuery} />
|
||||
)}
|
||||
</div>
|
||||
</Split>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { detectOS } from 'lib/helpers'
|
||||
export type UtilityActionsProps = {
|
||||
id: string
|
||||
isExecuting?: boolean
|
||||
executeQuery?: (overrideSql?: string) => void
|
||||
executeQuery: () => void
|
||||
}
|
||||
|
||||
const UtilityActions = ({ id, isExecuting = false, executeQuery }: UtilityActionsProps) => {
|
||||
@@ -20,7 +20,7 @@ const UtilityActions = ({ id, isExecuting = false, executeQuery }: UtilityAction
|
||||
{IS_PLATFORM && <FavoriteButton id={id} />}
|
||||
<SizeToggleButton id={id} />
|
||||
<Button
|
||||
onClick={() => executeQuery?.()}
|
||||
onClick={() => executeQuery()}
|
||||
disabled={isExecuting}
|
||||
loading={isExecuting}
|
||||
type="default"
|
||||
|
||||
@@ -5,7 +5,7 @@ import UtilityTabResults from './UtilityTabResults'
|
||||
export type UtilityPanelProps = {
|
||||
id: string
|
||||
isExecuting?: boolean
|
||||
executeQuery?: (overrideSql?: string) => void
|
||||
executeQuery: () => void
|
||||
}
|
||||
|
||||
const UtilityPanel = ({ id, isExecuting, executeQuery }: UtilityPanelProps) => {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { FC } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Input, Tabs } from 'ui'
|
||||
import { pluckObjectFields } from 'lib/helpers'
|
||||
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
|
||||
|
||||
import { useTelemetryProps } from 'common'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import ResetDbPassword from './ResetDbPassword'
|
||||
|
||||
const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
const { data, isLoading, isError } = useProjectSettingsQuery({ projectRef })
|
||||
const router = useRouter()
|
||||
const telemetryProps = useTelemetryProps()
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
@@ -102,6 +107,16 @@ const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
|
||||
const connectionInfo = pluckObjectFields(project, DB_FIELDS)
|
||||
|
||||
const handleCopy = (labelValue?: string) =>
|
||||
Telemetry.sendEvent(
|
||||
{
|
||||
category: 'settings',
|
||||
action: 'copy_connection_string',
|
||||
label: labelValue ? labelValue : '',
|
||||
},
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
const uriConnString =
|
||||
`postgresql://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
|
||||
`${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
|
||||
@@ -136,6 +151,9 @@ const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
disabled
|
||||
value={connectionInfo.db_host}
|
||||
label="Host"
|
||||
onCopy={() => {
|
||||
handleCopy('Host')
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
@@ -194,15 +212,39 @@ const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
<Panel.Content>
|
||||
<Tabs type="underlined" size="small">
|
||||
<Tabs.Panel id="psql" label="PSQL">
|
||||
<Input copy readOnly disabled value={psqlConnString} />
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
value={psqlConnString}
|
||||
onCopy={() => {
|
||||
handleCopy('PSQL')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel id="uri" label="URI">
|
||||
<Input copy readOnly disabled value={uriConnString} />
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
value={uriConnString}
|
||||
onCopy={() => {
|
||||
handleCopy('URI')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel id="golang" label="Golang">
|
||||
<Input copy readOnly disabled value={golangConnString} />
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
value={golangConnString}
|
||||
onCopy={() => {
|
||||
handleCopy('Golang')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel id="jdbc" label="JDBC">
|
||||
@@ -216,6 +258,9 @@ const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
}:${connectionInfo.db_port.toString()}` +
|
||||
`/${connectionInfo.db_name}?user=${connectionInfo.db_user}&password=[YOUR-PASSWORD]`
|
||||
}
|
||||
onCopy={() => {
|
||||
handleCopy('JDBC')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
@@ -229,15 +274,34 @@ const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
`Server=${connectionInfo.db_host};Port=${connectionInfo.db_port.toString()};` +
|
||||
`Database=${connectionInfo.db_name}`
|
||||
}
|
||||
onCopy={() => {
|
||||
handleCopy('.NET')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel id="nodejs" label="Nodejs">
|
||||
<Input copy readOnly disabled value={uriConnString} />
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
value={uriConnString}
|
||||
onCopy={() => {
|
||||
handleCopy('Nodejs')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel id="php" label="PHP">
|
||||
<Input copy readOnly disabled value={golangConnString} />
|
||||
<Input
|
||||
copy
|
||||
readOnly
|
||||
disabled
|
||||
value={golangConnString}
|
||||
onCopy={() => {
|
||||
handleCopy('PHP')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
|
||||
<Tabs.Panel id="python" label="Python">
|
||||
@@ -250,6 +314,9 @@ const DatabaseSettings: FC<any> = ({ projectRef }) => {
|
||||
` host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
|
||||
` database=${connectionInfo.db_name}`
|
||||
}
|
||||
onCopy={() => {
|
||||
handleCopy('Python')
|
||||
}}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
@@ -3,12 +3,11 @@ import { FC, ReactNode } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
import { auth, STORAGE_KEY } from 'lib/gotrue'
|
||||
import { useSignOut } from 'lib/auth'
|
||||
import { useStore, withAuth, useFlag } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import WithSidebar from './WithSidebar'
|
||||
import { SidebarSection } from './AccountLayout.types'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
@@ -22,16 +21,15 @@ interface Props {
|
||||
const AccountLayout: FC<Props> = ({ children, title, breadcrumbs }) => {
|
||||
const router = useRouter()
|
||||
const { app, ui } = useStore()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const maxHeight = ongoingIncident ? 'calc(100vh - 44px)' : '100vh'
|
||||
|
||||
const signOut = useSignOut()
|
||||
const onClickLogout = async () => {
|
||||
await auth.signOut()
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
await signOut()
|
||||
|
||||
await router.push('/sign-in')
|
||||
await queryClient.resetQueries()
|
||||
}
|
||||
|
||||
const organizationsLinks = app.organizations
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useParams } from 'common'
|
||||
import ConfirmationModal from 'components/ui/ConfirmationModal'
|
||||
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
|
||||
@@ -21,14 +21,26 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
|
||||
const { ref, id: activeId } = useParams()
|
||||
const { id, name } = tabInfo || {}
|
||||
const isActive = id === activeId
|
||||
const activeItemRef = useRef<HTMLElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// scroll to active item
|
||||
if (isActive && activeItemRef.current) {
|
||||
// race condition hack
|
||||
setTimeout(() => {
|
||||
activeItemRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={clsx(
|
||||
'flex items-center justify-between rounded-md group',
|
||||
isActive && 'text-scale-1200 bg-scale-300'
|
||||
isActive && 'text-scale-1200 bg-scale-400 dark:bg-scale-300 -active'
|
||||
)}
|
||||
ref={isActive ? (activeItemRef as React.RefObject<HTMLDivElement>) : null}
|
||||
>
|
||||
<Link href={`/project/${ref}/sql/${id}`}>
|
||||
<a className="py-1 px-3 w-full">
|
||||
@@ -127,14 +139,12 @@ const QueryItemActions = observer(({ tabInfo }: { tabInfo: SqlSnippet }) => {
|
||||
) : (
|
||||
<Button as="span" type="text" style={{ padding: '3px' }} />
|
||||
)}
|
||||
|
||||
<RenameQueryModal
|
||||
snippet={tabInfo}
|
||||
visible={renameModalOpen}
|
||||
onCancel={onCloseRenameModal}
|
||||
onComplete={onCloseRenameModal}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
header="Confirm to delete"
|
||||
buttonLabel="Delete query"
|
||||
|
||||
@@ -61,7 +61,6 @@ const SideBarContent = observer(() => {
|
||||
filterString.length === 0
|
||||
? queries
|
||||
: queries.filter((tab) => tab.name.toLowerCase().includes(filterString.toLowerCase()))
|
||||
|
||||
const canCreateSQLSnippet = checkPermissions(PermissionAction.CREATE, 'user_content', {
|
||||
resource: { type: 'sql', owner_id: profile?.id },
|
||||
subject: { id: profile?.id },
|
||||
@@ -112,6 +111,8 @@ const SideBarContent = observer(() => {
|
||||
<Dropdown
|
||||
align="end"
|
||||
side="bottom"
|
||||
sideOffset={3}
|
||||
className="max-w-[210px]"
|
||||
overlay={[
|
||||
<Dropdown.Item
|
||||
key="new-ai-query"
|
||||
@@ -180,21 +181,25 @@ const SideBarContent = observer(() => {
|
||||
<div className="editor-product-menu">
|
||||
<Menu.Group title="Favorites" />
|
||||
<div className="space-y-1">
|
||||
{favouriteTabs.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
{favouriteTabs
|
||||
.sort((a, b) => a.name?.localeCompare(b.name)) // first alphabetical
|
||||
.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem 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) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
<div className="space-y-1 pb-8">
|
||||
{queryTabs
|
||||
.sort((a, b) => a.name?.localeCompare(b.name)) // first alphabetical
|
||||
.map((tabInfo) => {
|
||||
const { id } = tabInfo || {}
|
||||
return <QueryItem key={id} tabInfo={tabInfo} />
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||