mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 07:39:07 +03:00
Merge branch 'master' of github.com:supabase/supabase
This commit is contained in:
commit
3a545b0d7a
28 files changed
+558
-413
No files matched your search
@@ -5,61 +5,41 @@ on:
|
||||
jobs:
|
||||
check-external:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
result: ${{ steps.is-member.outputs.result }}
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Get deps
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y jq
|
||||
- name: Get org members
|
||||
id: is-member
|
||||
env:
|
||||
GH_ORG: supabase
|
||||
GH_USER: ${{ github.event.issue.user.login }}
|
||||
GH_TOKEN: ${{ secrets.SB_ORG_MEMBERSHIP_PAT }}
|
||||
run: |
|
||||
gh api /orgs/supabase/members/$GH_USER
|
||||
- if: failure()
|
||||
name: is-member
|
||||
run: |
|
||||
echo "User is not a member of Supabase org"
|
||||
echo "result=false" >> "$GITHUB_OUTPUT"
|
||||
- if: success()
|
||||
run: |
|
||||
echo "User is a member of Supabase org"
|
||||
echo "result=true" >> "$GITHUB_OUTPUT"
|
||||
- name: Label contributor
|
||||
id: label-contributor
|
||||
if: ${{ steps.is-member.outputs.result == 'false' }}
|
||||
- name: Check if organization member
|
||||
id: is-org-member
|
||||
uses: JamesSingleton/is-organization-member@1.0.0
|
||||
with:
|
||||
organization: ${{ github.repository_owner }}
|
||||
username: ${{ github.event.issue.user.login }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: label-member
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
USERNAME: ${{ github.event.issue.user.login }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
LABELS: external-issue
|
||||
run: |
|
||||
echo "applying external label"
|
||||
gh issue edit $NUMBER --add-label $LABELS
|
||||
- name: Label internal
|
||||
id: label-internal
|
||||
if: ${{ steps.is-member.outputs.result == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
USERNAME: ${{ github.event.issue.user.login }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
LABELS: internal-issue
|
||||
run: |
|
||||
echo "applying internal label"
|
||||
gh issue edit $NUMBER --add-label $LABELS
|
||||
if [ ${{ steps.is-org-member.outputs.result }} != "true" ]; then
|
||||
echo "User is outside of organization, labeling external"
|
||||
gh issue edit "$NUMBER" --add-label "external-issue"
|
||||
else
|
||||
echo "User is within the organization, labeling internal"
|
||||
gh issue edit "$NUMBER" --add-label "internal-issue"
|
||||
fi
|
||||
triage-new:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Label triage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
NUMBER: ${{ github.event.issue.number }}
|
||||
LABELS: to-triage
|
||||
run: |
|
||||
echo "applying triage label"
|
||||
gh issue edit $NUMBER --add-label $LABELS
|
||||
echo "Applying triage label for new issue"
|
||||
gh issue edit "$NUMBER" --add-label "$LABELS"
|
||||
|
||||
@@ -2831,7 +2831,8 @@ commands:
|
||||
Activates the custom hostname configuration for a project.
|
||||
|
||||
This reconfigures your Supabase project to respond to requests on your custom hostname.
|
||||
After the custom hostname is activated, your project's auth services will no longer function on the Supabase-provisioned subdomain.
|
||||
|
||||
After the custom hostname is activated, your project's third-party auth providers will no longer function on the Supabase-provisioned subdomain. Please refer to [Prepare to activate your domain](/docs/guides/platform/custom-domains#prepare-to-activate-your-domain) section in our documentation to learn more about the steps you need to follow.
|
||||
tags: []
|
||||
links: []
|
||||
usage: supabase domains activate
|
||||
|
||||
@@ -14,9 +14,18 @@ export const inferProjectStatus = (project: ProjectInfo) => {
|
||||
case PROJECT_STATUS.INACTIVE:
|
||||
status = 'isPaused'
|
||||
break
|
||||
case PROJECT_STATUS.PAUSE_FAILED:
|
||||
status = 'isPauseFailed'
|
||||
break
|
||||
case PROJECT_STATUS.RESTARTING:
|
||||
status = 'isRestarting'
|
||||
break
|
||||
case PROJECT_STATUS.RESTORING:
|
||||
status = 'isRestoring'
|
||||
break
|
||||
case PROJECT_STATUS.RESTORE_FAILED:
|
||||
status = 'isRestoreFailed'
|
||||
break
|
||||
case PROJECT_STATUS.UNKNOWN:
|
||||
case PROJECT_STATUS.COMING_UP:
|
||||
status = 'isComingUp'
|
||||
@@ -29,6 +38,9 @@ export type InferredProjectStatus =
|
||||
| 'isHealthy'
|
||||
| 'isPausing'
|
||||
| 'isPaused'
|
||||
| 'isPauseFailed'
|
||||
| 'isRestarting'
|
||||
| 'isRestoring'
|
||||
| 'isRestoreFailed'
|
||||
| 'isComingUp'
|
||||
| undefined
|
||||
@@ -39,8 +39,11 @@ export const ProjectCardStatus = ({
|
||||
const getTitle = () => {
|
||||
if (projectStatus === 'isPaused') return 'Project is paused'
|
||||
if (projectStatus === 'isPausing') return 'Project is pausing'
|
||||
if (projectStatus === 'isRestarting') return 'Project is restarting'
|
||||
if (projectStatus === 'isComingUp') return 'Project is coming up'
|
||||
if (projectStatus === 'isRestoring') return 'Project is restoring'
|
||||
if (projectStatus === 'isRestoreFailed') return 'Project restore failed'
|
||||
if (projectStatus === 'isPauseFailed') return 'Project pause failed'
|
||||
|
||||
if (!resourceWarnings) return undefined
|
||||
|
||||
@@ -55,8 +58,11 @@ export const ProjectCardStatus = ({
|
||||
const getDescription = () => {
|
||||
if (projectStatus === 'isPaused') return 'This project will not accept requests until resumed'
|
||||
if (projectStatus === 'isPausing') return 'The pause process will complete in a few minutes'
|
||||
if (projectStatus === 'isRestarting') return 'Your project will be ready in a few minutes'
|
||||
if (projectStatus === 'isComingUp') return 'Your project will be ready in a few minutes'
|
||||
if (projectStatus === 'isRestoring') return 'Your project will be ready in a few minutes'
|
||||
if (projectStatus === 'isRestoreFailed') return 'Please contact support for assistance'
|
||||
if (projectStatus === 'isPauseFailed') return 'Please contact support for assistance'
|
||||
|
||||
if (!resourceWarnings) return undefined
|
||||
|
||||
@@ -95,7 +101,9 @@ export const ProjectCardStatus = ({
|
||||
>
|
||||
{projectStatus === 'isPaused' || projectStatus === 'isPausing' ? (
|
||||
<IconPauseCircle strokeWidth={1.5} size={12} />
|
||||
) : projectStatus === 'isRestoring' || projectStatus === 'isComingUp' ? (
|
||||
) : projectStatus === 'isRestoring' ||
|
||||
projectStatus === 'isComingUp' ||
|
||||
projectStatus === 'isRestarting' ? (
|
||||
<RefreshCcw strokeWidth={1.5} size={12} />
|
||||
) : (
|
||||
<AlertTriangle strokeWidth={1.5} size={12} />
|
||||
|
||||
+85
@@ -7,6 +7,7 @@ export interface TaxId {
|
||||
name: string
|
||||
type: string
|
||||
country: string
|
||||
countryIso2: string
|
||||
placeholder: string
|
||||
vatPrefix?: string
|
||||
}
|
||||
@@ -17,6 +18,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
type: 'ae_trn',
|
||||
country: 'United Arab Emirates',
|
||||
placeholder: '123456789012345',
|
||||
countryIso2: 'AE',
|
||||
},
|
||||
{
|
||||
name: 'AT VAT',
|
||||
@@ -24,30 +26,35 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Austria',
|
||||
placeholder: 'ATU12345678',
|
||||
vatPrefix: 'ATU',
|
||||
countryIso2: 'AT',
|
||||
},
|
||||
{
|
||||
name: 'AD NRT',
|
||||
type: 'ad_nrt',
|
||||
country: 'Andorra',
|
||||
placeholder: 'A-123456-Z',
|
||||
countryIso2: 'AD',
|
||||
},
|
||||
{
|
||||
name: 'AR CUIT',
|
||||
type: 'ar_cuit',
|
||||
country: 'Argentina',
|
||||
placeholder: '12-3456789-01',
|
||||
countryIso2: 'AR',
|
||||
},
|
||||
{
|
||||
name: 'AU ABN',
|
||||
type: 'au_abn',
|
||||
country: 'Australia',
|
||||
placeholder: '12345678912',
|
||||
countryIso2: 'AU',
|
||||
},
|
||||
{
|
||||
name: 'AU ARN',
|
||||
type: 'au_arn',
|
||||
country: 'Australia',
|
||||
placeholder: '123456789123',
|
||||
countryIso2: 'AU',
|
||||
},
|
||||
{
|
||||
name: 'BE VAT',
|
||||
@@ -55,12 +62,14 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Belgium',
|
||||
placeholder: 'BE0123456789',
|
||||
vatPrefix: 'BE',
|
||||
countryIso2: 'BE',
|
||||
},
|
||||
{
|
||||
name: 'BO TIN',
|
||||
type: 'bo_tin',
|
||||
country: 'Bolivia',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'BO',
|
||||
},
|
||||
{
|
||||
name: 'BG VAT',
|
||||
@@ -68,54 +77,63 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Bulgaria',
|
||||
placeholder: 'BG0123456789',
|
||||
vatPrefix: 'BG',
|
||||
countryIso2: 'BG',
|
||||
},
|
||||
{
|
||||
name: 'BR CNPJ',
|
||||
type: 'br_cnpj',
|
||||
country: 'Brazil',
|
||||
placeholder: '01.234.456/5432-10',
|
||||
countryIso2: 'BR',
|
||||
},
|
||||
{
|
||||
name: 'BR CPF',
|
||||
type: 'br_cpf',
|
||||
country: 'Brazil',
|
||||
placeholder: '123.456.789-87',
|
||||
countryIso2: 'BR',
|
||||
},
|
||||
{
|
||||
name: 'CA BN',
|
||||
type: 'ca_bn',
|
||||
country: 'Canada',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'CA',
|
||||
},
|
||||
{
|
||||
name: 'CA GST/HST',
|
||||
type: 'ca_gst_hst',
|
||||
country: 'Canada',
|
||||
placeholder: '123456789RT0002',
|
||||
countryIso2: 'CA',
|
||||
},
|
||||
{
|
||||
name: 'CA PST-BC',
|
||||
type: 'ca_pst_bc',
|
||||
country: 'Canada',
|
||||
placeholder: 'PST-1234-5678',
|
||||
countryIso2: 'CA',
|
||||
},
|
||||
{
|
||||
name: 'CA PST-MB',
|
||||
type: 'ca_pst_mb',
|
||||
country: 'Canada',
|
||||
placeholder: '123456-7',
|
||||
countryIso2: 'CA',
|
||||
},
|
||||
{
|
||||
name: 'CA PST-SK',
|
||||
type: 'ca_pst_mb',
|
||||
country: 'Canada',
|
||||
placeholder: '1234567',
|
||||
countryIso2: 'CA',
|
||||
},
|
||||
{
|
||||
name: 'CA QST',
|
||||
type: 'ca_qst',
|
||||
country: 'Canada',
|
||||
placeholder: '1234567890TQ1234',
|
||||
countryIso2: 'CA',
|
||||
},
|
||||
{
|
||||
name: 'CH VAT',
|
||||
@@ -123,30 +141,35 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Switzerland',
|
||||
placeholder: 'CHE-123.456.789 MWST',
|
||||
vatPrefix: 'CHE',
|
||||
countryIso2: 'CH',
|
||||
},
|
||||
{
|
||||
name: 'CL TIN',
|
||||
type: 'cl_tin',
|
||||
country: 'Chile',
|
||||
placeholder: '12.345.678-K',
|
||||
countryIso2: 'CL',
|
||||
},
|
||||
{
|
||||
name: 'CN TIN',
|
||||
type: 'cn_tin',
|
||||
country: 'China',
|
||||
placeholder: '123456789012345678',
|
||||
countryIso2: 'CN',
|
||||
},
|
||||
{
|
||||
name: 'CO NIT',
|
||||
type: 'co_nit',
|
||||
country: 'Colombia',
|
||||
placeholder: '123.456.789-0',
|
||||
countryIso2: 'CO',
|
||||
},
|
||||
{
|
||||
name: 'CR TIN',
|
||||
type: 'cr_tin',
|
||||
country: 'Costa Rica',
|
||||
placeholder: '1-234-567890',
|
||||
countryIso2: 'CR',
|
||||
},
|
||||
{
|
||||
name: 'CY VAT',
|
||||
@@ -154,6 +177,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Cyprus',
|
||||
placeholder: 'CY12345678Z',
|
||||
vatPrefix: 'CY',
|
||||
countryIso2: 'CY',
|
||||
},
|
||||
{
|
||||
name: 'CZ VAT',
|
||||
@@ -161,6 +185,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Czech Republic',
|
||||
placeholder: 'CZ1234567890',
|
||||
vatPrefix: 'CZ',
|
||||
countryIso2: 'CZ',
|
||||
},
|
||||
{
|
||||
name: 'DE VAT',
|
||||
@@ -168,6 +193,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Germany',
|
||||
placeholder: 'DE123456789',
|
||||
vatPrefix: 'DE',
|
||||
countryIso2: 'DE',
|
||||
},
|
||||
{
|
||||
name: 'DK VAT',
|
||||
@@ -175,12 +201,14 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Denmark',
|
||||
placeholder: 'DK12345678',
|
||||
vatPrefix: 'DK',
|
||||
countryIso2: 'DK',
|
||||
},
|
||||
{
|
||||
name: 'DO RCN',
|
||||
type: 'do_rcn',
|
||||
country: 'Dominican Republic',
|
||||
placeholder: '123-4567890-1',
|
||||
countryIso2: 'DO',
|
||||
},
|
||||
{
|
||||
name: 'EE VAT',
|
||||
@@ -188,12 +216,14 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Estonia',
|
||||
placeholder: 'EE123456789',
|
||||
vatPrefix: 'EE',
|
||||
countryIso2: 'EE',
|
||||
},
|
||||
{
|
||||
name: 'ES CIF',
|
||||
type: 'es_cif',
|
||||
country: 'Spain',
|
||||
placeholder: 'A12345678',
|
||||
countryIso2: 'ES',
|
||||
},
|
||||
{
|
||||
name: 'ES VAT',
|
||||
@@ -201,6 +231,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Spain',
|
||||
placeholder: 'ESX1234567Z',
|
||||
vatPrefix: 'ES',
|
||||
countryIso2: 'ES',
|
||||
},
|
||||
{
|
||||
name: 'FI VAT',
|
||||
@@ -208,6 +239,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Finland',
|
||||
placeholder: 'FI12345678',
|
||||
vatPrefix: 'FI',
|
||||
countryIso2: 'FI',
|
||||
},
|
||||
{
|
||||
name: 'FR VAT',
|
||||
@@ -215,6 +247,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'France',
|
||||
placeholder: 'FRAB123456789',
|
||||
vatPrefix: 'FR',
|
||||
countryIso2: 'FR',
|
||||
},
|
||||
{
|
||||
name: 'GB VAT',
|
||||
@@ -222,17 +255,20 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'United Kingdom',
|
||||
placeholder: 'GB123456789',
|
||||
vatPrefix: 'GB',
|
||||
countryIso2: 'GB',
|
||||
},
|
||||
{
|
||||
name: 'GE VAT',
|
||||
type: 'ge_vat',
|
||||
country: 'Georgia',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'GE',
|
||||
},
|
||||
{
|
||||
name: 'EL VAT',
|
||||
type: 'eu_vat',
|
||||
country: 'Greece',
|
||||
countryIso2: 'GR',
|
||||
placeholder: 'EL123456789',
|
||||
vatPrefix: 'EL',
|
||||
},
|
||||
@@ -241,6 +277,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
type: 'hk_br',
|
||||
country: 'Hong Kong SAR China',
|
||||
placeholder: '12345678',
|
||||
countryIso2: 'HK',
|
||||
},
|
||||
{
|
||||
name: 'HR VAT',
|
||||
@@ -248,6 +285,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Croatia',
|
||||
placeholder: 'HR12345678912',
|
||||
vatPrefix: 'HR',
|
||||
countryIso2: 'HR',
|
||||
},
|
||||
{
|
||||
name: 'HU VAT',
|
||||
@@ -255,12 +293,14 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Hungary',
|
||||
placeholder: 'HU12345678912',
|
||||
vatPrefix: 'HU',
|
||||
countryIso2: 'HU',
|
||||
},
|
||||
{
|
||||
name: 'ID NPWP',
|
||||
type: 'id_npwp',
|
||||
country: 'Indonesia',
|
||||
placeholder: '12.345.678.9-012.345',
|
||||
countryIso2: 'ID',
|
||||
},
|
||||
{
|
||||
name: 'IE VAT',
|
||||
@@ -268,24 +308,28 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Ireland',
|
||||
placeholder: 'IE1234567AB',
|
||||
vatPrefix: 'IE',
|
||||
countryIso2: 'IE',
|
||||
},
|
||||
{
|
||||
name: 'IL VAT',
|
||||
type: 'il_vat',
|
||||
country: 'Israel',
|
||||
placeholder: '000012345',
|
||||
countryIso2: 'IL',
|
||||
},
|
||||
{
|
||||
name: 'IN GST',
|
||||
type: 'in_gst',
|
||||
country: 'India',
|
||||
placeholder: '12ABCDE3456FGZH',
|
||||
countryIso2: 'IN',
|
||||
},
|
||||
{
|
||||
name: 'IS VAT',
|
||||
type: 'is_vat',
|
||||
country: 'Iceland',
|
||||
placeholder: '123456',
|
||||
countryIso2: 'IS',
|
||||
},
|
||||
{
|
||||
name: 'IT VAT',
|
||||
@@ -293,36 +337,42 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Italy',
|
||||
placeholder: 'IT12345678912',
|
||||
vatPrefix: 'IT',
|
||||
countryIso2: 'IT',
|
||||
},
|
||||
{
|
||||
name: 'JP CN',
|
||||
type: 'jp_cn',
|
||||
country: 'Japan',
|
||||
placeholder: '1234567891234',
|
||||
countryIso2: 'JP',
|
||||
},
|
||||
{
|
||||
name: 'KE PIN',
|
||||
type: 'ke_pin',
|
||||
country: 'Kenya',
|
||||
placeholder: 'P000111111A',
|
||||
countryIso2: 'KE',
|
||||
},
|
||||
{
|
||||
name: 'JP RN',
|
||||
type: 'jp_rn',
|
||||
country: 'Japan',
|
||||
placeholder: '12345',
|
||||
countryIso2: 'JP',
|
||||
},
|
||||
{
|
||||
name: 'KR BRN',
|
||||
type: 'kr_brn',
|
||||
country: 'Korea',
|
||||
placeholder: '123-45-67890',
|
||||
countryIso2: 'Kr',
|
||||
},
|
||||
{
|
||||
name: 'LI UID',
|
||||
type: 'li_uid',
|
||||
country: 'Liechtenstein',
|
||||
placeholder: 'CHE123456789',
|
||||
countryIso2: 'LI',
|
||||
},
|
||||
{
|
||||
name: 'LT VAT',
|
||||
@@ -330,6 +380,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Lithuania',
|
||||
placeholder: 'LT123456789123',
|
||||
vatPrefix: 'LT',
|
||||
countryIso2: 'LT',
|
||||
},
|
||||
{
|
||||
name: 'LU VAT',
|
||||
@@ -337,6 +388,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Luxembourg',
|
||||
placeholder: 'LU12345678',
|
||||
vatPrefix: 'LU',
|
||||
countryIso2: 'LU',
|
||||
},
|
||||
{
|
||||
name: 'LV VAT',
|
||||
@@ -344,6 +396,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Latvia',
|
||||
placeholder: 'LV12345678912',
|
||||
vatPrefix: 'LV',
|
||||
countryIso2: 'LV',
|
||||
},
|
||||
{
|
||||
name: 'MT VAT',
|
||||
@@ -351,30 +404,35 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Malta',
|
||||
placeholder: 'MT12345678',
|
||||
vatPrefix: 'MT',
|
||||
countryIso2: 'MT',
|
||||
},
|
||||
{
|
||||
name: 'MX RFC',
|
||||
type: 'mx_rfc',
|
||||
country: 'Mexico',
|
||||
placeholder: 'ABC010203AB9',
|
||||
countryIso2: 'MX',
|
||||
},
|
||||
{
|
||||
name: 'MY FRP',
|
||||
type: 'my_frp',
|
||||
country: 'Malaysia',
|
||||
placeholder: '12345678',
|
||||
countryIso2: 'MY',
|
||||
},
|
||||
{
|
||||
name: 'MY ITN',
|
||||
type: 'my_itn',
|
||||
country: 'Malaysia',
|
||||
placeholder: 'C 1234567890',
|
||||
countryIso2: 'MY',
|
||||
},
|
||||
{
|
||||
name: 'MY SST',
|
||||
type: 'my_sst',
|
||||
country: 'Malaysia',
|
||||
placeholder: 'A12-3456-78912345',
|
||||
countryIso2: 'MY',
|
||||
},
|
||||
{
|
||||
name: 'NL VAT',
|
||||
@@ -382,30 +440,35 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Netherlands',
|
||||
placeholder: 'NL123456789B12',
|
||||
vatPrefix: 'NL',
|
||||
countryIso2: 'NL',
|
||||
},
|
||||
{
|
||||
name: 'NO VAT',
|
||||
type: 'no_vat',
|
||||
country: 'Norway',
|
||||
placeholder: '123456789MVA',
|
||||
countryIso2: 'NO',
|
||||
},
|
||||
{
|
||||
name: 'PE RUC',
|
||||
type: 'pe_ruc',
|
||||
country: 'Peru',
|
||||
placeholder: '12345678901',
|
||||
countryIso2: 'PE',
|
||||
},
|
||||
{
|
||||
name: 'PH TIN',
|
||||
type: 'ph_tin',
|
||||
country: 'Philippines',
|
||||
placeholder: '123456789012',
|
||||
countryIso2: 'PH',
|
||||
},
|
||||
{
|
||||
name: 'NZ GST',
|
||||
type: 'nz_gst',
|
||||
country: 'New Zealand',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'NZ',
|
||||
},
|
||||
{
|
||||
name: 'PL VAT',
|
||||
@@ -413,6 +476,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Poland',
|
||||
placeholder: 'PL1234567890',
|
||||
vatPrefix: 'PL',
|
||||
countryIso2: 'PL',
|
||||
},
|
||||
{
|
||||
name: 'PT VAT',
|
||||
@@ -420,6 +484,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Portugal',
|
||||
placeholder: 'PT123456789',
|
||||
vatPrefix: 'PT',
|
||||
countryIso2: 'PT',
|
||||
},
|
||||
{
|
||||
name: 'RO VAT',
|
||||
@@ -427,30 +492,35 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Romania',
|
||||
placeholder: 'RO1234567891',
|
||||
vatPrefix: 'RO',
|
||||
countryIso2: 'RO',
|
||||
},
|
||||
{
|
||||
name: 'RU INN',
|
||||
type: 'ru_inn',
|
||||
country: 'Russia',
|
||||
placeholder: '1234567891',
|
||||
countryIso2: 'RU',
|
||||
},
|
||||
{
|
||||
name: 'RU KPP',
|
||||
type: 'ru_kpp',
|
||||
country: 'Russia',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'RU',
|
||||
},
|
||||
{
|
||||
name: 'SA VAT',
|
||||
type: 'sa_vat',
|
||||
country: 'Saudi Arabia',
|
||||
placeholder: '123456789012345',
|
||||
countryIso2: 'SA',
|
||||
},
|
||||
{
|
||||
name: 'RS PIB',
|
||||
type: 'rs_pib',
|
||||
country: 'Serbia',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'RS',
|
||||
},
|
||||
{
|
||||
name: 'SE VAT',
|
||||
@@ -458,18 +528,21 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Sweden',
|
||||
placeholder: 'SE123456789123',
|
||||
vatPrefix: 'SE',
|
||||
countryIso2: 'SE',
|
||||
},
|
||||
{
|
||||
name: 'SG GST',
|
||||
type: 'sg_gst',
|
||||
country: 'Singapore',
|
||||
placeholder: 'M12345678X',
|
||||
countryIso2: 'SG',
|
||||
},
|
||||
{
|
||||
name: 'SG UEN',
|
||||
type: 'sg_uen',
|
||||
country: 'Singapore',
|
||||
placeholder: '123456789F',
|
||||
countryIso2: 'SG',
|
||||
},
|
||||
{
|
||||
name: 'SI VAT',
|
||||
@@ -477,6 +550,7 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Slovenia',
|
||||
placeholder: 'SI12345678',
|
||||
vatPrefix: 'SI',
|
||||
countryIso2: 'SI',
|
||||
},
|
||||
{
|
||||
name: 'SK VAT',
|
||||
@@ -484,36 +558,42 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'Slovakia',
|
||||
placeholder: 'SK1234567891',
|
||||
vatPrefix: 'SK',
|
||||
countryIso2: 'SK',
|
||||
},
|
||||
{
|
||||
name: 'TH VAT',
|
||||
type: 'th_vat',
|
||||
country: 'Thailand',
|
||||
placeholder: '1234567891234',
|
||||
countryIso2: 'TH',
|
||||
},
|
||||
{
|
||||
name: 'TR TIN',
|
||||
type: 'tr_tin',
|
||||
country: 'Turkey',
|
||||
placeholder: '0123456789',
|
||||
countryIso2: 'TR',
|
||||
},
|
||||
{
|
||||
name: 'TW VAT',
|
||||
type: 'tw_vat',
|
||||
country: 'Taiwan',
|
||||
placeholder: '12345678',
|
||||
countryIso2: 'TW',
|
||||
},
|
||||
{
|
||||
name: 'UA VAT',
|
||||
type: 'ua_vat',
|
||||
country: 'Ukraine',
|
||||
placeholder: '123456789',
|
||||
countryIso2: 'UA',
|
||||
},
|
||||
{
|
||||
name: 'US EIN',
|
||||
type: 'us_ein',
|
||||
country: 'United States',
|
||||
placeholder: '12-3456789',
|
||||
countryIso2: 'US',
|
||||
},
|
||||
{
|
||||
name: 'XI VAT',
|
||||
@@ -521,29 +601,34 @@ export const TAX_IDS: TaxId[] = [
|
||||
country: 'United Kingdom (Northern Ireland)',
|
||||
placeholder: 'XI123456789',
|
||||
vatPrefix: 'XI',
|
||||
countryIso2: 'GB',
|
||||
},
|
||||
{
|
||||
name: 'ZA VAT',
|
||||
type: 'za_vat',
|
||||
country: 'South Africa',
|
||||
placeholder: '4123456789',
|
||||
countryIso2: 'ZA',
|
||||
},
|
||||
{
|
||||
name: 'UY RUC',
|
||||
type: 'uy_ruc',
|
||||
country: 'Uruguay',
|
||||
placeholder: '123456789012',
|
||||
countryIso2: 'UY',
|
||||
},
|
||||
{
|
||||
name: 'VE RIF',
|
||||
type: 've_rif',
|
||||
country: 'Venezuela',
|
||||
placeholder: 'A-12345678-9',
|
||||
countryIso2: 'VE',
|
||||
},
|
||||
{
|
||||
name: 'VN TIN',
|
||||
type: 'vn_tin',
|
||||
country: 'Vietnam',
|
||||
placeholder: '1234567890',
|
||||
countryIso2: 'VN',
|
||||
},
|
||||
]
|
||||
@@ -59,9 +59,9 @@ const TaxID = () => {
|
||||
type: taxId.type,
|
||||
value: taxId.value,
|
||||
name:
|
||||
taxId.type === 'eu_vat'
|
||||
? `${taxId.country} VAT`
|
||||
: TAX_IDS.find((option) => option.type === taxId.type)?.name ?? '',
|
||||
TAX_IDS.find(
|
||||
(option) => option.type === taxId.type && option.countryIso2 === taxId.country
|
||||
)?.name ?? '',
|
||||
}
|
||||
: { type: '', value: '', name: '' }
|
||||
|
||||
@@ -94,7 +94,7 @@ const TaxID = () => {
|
||||
type?.length && value?.length
|
||||
? {
|
||||
type: type,
|
||||
value: sanitizeTaxIdValue({ type, value }),
|
||||
value: sanitizeTaxIdValue({ value, name: form.getValues().name }),
|
||||
}
|
||||
: null,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TAX_IDS } from './TaxID.constants'
|
||||
|
||||
export const sanitizeTaxIdValue = (taxId: { type: string; value: string }) => {
|
||||
const selectedTaxId = TAX_IDS.find((option) => option.type === taxId.type)
|
||||
export const sanitizeTaxIdValue = (taxId: { name: string; value: string }) => {
|
||||
const selectedTaxId = TAX_IDS.find((option) => option.name === taxId.name)
|
||||
|
||||
const vatIdPrefix = selectedTaxId?.vatPrefix
|
||||
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import {
|
||||
} from 'components/layouts/ProjectLayout/ProjectContext'
|
||||
import { useProjectRestartMutation } from 'data/projects/project-restart-mutation'
|
||||
import { useProjectRestartServicesMutation } from 'data/projects/project-restart-services-mutation'
|
||||
import { setProjectPostgrestStatus } from 'data/projects/projects-query'
|
||||
import { setProjectStatus } from 'data/projects/projects-query'
|
||||
import { useCheckPermissions, useFlag } from 'hooks'
|
||||
import {
|
||||
Button,
|
||||
@@ -76,7 +76,7 @@ const RestartServerButton = () => {
|
||||
}
|
||||
|
||||
const onRestartSuccess = () => {
|
||||
setProjectPostgrestStatus(queryClient, projectRef, 'OFFLINE')
|
||||
setProjectStatus(queryClient, projectRef, 'RESTARTING')
|
||||
toast.success('Restarting server...')
|
||||
router.push(`/project/${projectRef}`)
|
||||
setServiceToRestart(undefined)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useRef, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
@@ -9,8 +8,6 @@ import { useResetPasswordMutation } from 'data/misc/reset-password-mutation'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { Button, Form, Input } from 'ui'
|
||||
|
||||
const WHITELIST_ERRORS = ['email must be an email']
|
||||
|
||||
const forgotPasswordSchema = object({
|
||||
email: string().email('Must be a valid email').required('Email is required'),
|
||||
})
|
||||
@@ -31,10 +28,6 @@ const ForgotPasswordForm = () => {
|
||||
setCaptchaToken(null)
|
||||
captchaRef.current?.resetCaptcha()
|
||||
toast.error(`Failed to send reset email: ${error.message}`)
|
||||
|
||||
if (!WHITELIST_ERRORS.some((e) => error.message.includes(e))) {
|
||||
Sentry.captureMessage('[CRITICAL] Failed to send reset password email: ' + error.message)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { AuthError } from '@supabase/auth-js'
|
||||
import type { Factor } from '@supabase/supabase-js'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { Lock } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { object, string } from 'yup'
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
|
||||
import { useTelemetryProps } from 'common'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
@@ -16,7 +14,7 @@ import { useMfaListFactorsQuery } from 'data/profile/mfa-list-factors-query'
|
||||
import { useSignOut } from 'lib/auth'
|
||||
import { getReturnToPath } from 'lib/gotrue'
|
||||
import Telemetry from 'lib/telemetry'
|
||||
import { Button, Form, IconLock, Input } from 'ui'
|
||||
import { Button, Form, Input } from 'ui'
|
||||
|
||||
const signInSchema = object({
|
||||
code: string().required('MFA Code is required'),
|
||||
@@ -36,7 +34,21 @@ const SignInMfaForm = () => {
|
||||
isSuccess: isSuccessFactors,
|
||||
isLoading: isLoadingFactors,
|
||||
} = useMfaListFactorsQuery()
|
||||
const { mutate: mfaChallengeAndVerify, isLoading, isSuccess } = useMfaChallengeAndVerifyMutation()
|
||||
const {
|
||||
mutate: mfaChallengeAndVerify,
|
||||
isLoading: isVerifying,
|
||||
isSuccess,
|
||||
} = useMfaChallengeAndVerifyMutation({
|
||||
onSuccess: async () => {
|
||||
Telemetry.sendEvent(
|
||||
{ category: 'account', action: 'sign_in', label: '' },
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
await queryClient.resetQueries()
|
||||
router.push(getReturnToPath())
|
||||
},
|
||||
})
|
||||
|
||||
const onClickLogout = async () => {
|
||||
await signOut()
|
||||
@@ -44,27 +56,8 @@ const SignInMfaForm = () => {
|
||||
}
|
||||
|
||||
const onSignIn = async ({ code }: { code: string }) => {
|
||||
const toastId = toast.loading('Signing in...')
|
||||
if (selectedFactor) {
|
||||
mfaChallengeAndVerify(
|
||||
{ factorId: selectedFactor.id, code, refreshFactors: false },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
toast.success('Signed in successfully!', { id: toastId })
|
||||
Telemetry.sendEvent(
|
||||
{ category: 'account', action: 'sign_in', label: '' },
|
||||
telemetryProps,
|
||||
router
|
||||
)
|
||||
await queryClient.resetQueries()
|
||||
router.push(getReturnToPath())
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Failed to sign in: ${(error as AuthError).message}`, { id: toastId })
|
||||
Sentry.captureMessage('[CRITICAL] Failed to sign in via MFA: ' + error.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
mfaChallengeAndVerify({ factorId: selectedFactor.id, code, refreshFactors: false })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +95,9 @@ const SignInMfaForm = () => {
|
||||
name="code"
|
||||
type="text"
|
||||
autoFocus
|
||||
icon={<IconLock />}
|
||||
icon={<Lock />}
|
||||
placeholder="XXXXXX"
|
||||
disabled={isLoading}
|
||||
disabled={isVerifying}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
autoCapitalize="none"
|
||||
@@ -121,7 +114,7 @@ const SignInMfaForm = () => {
|
||||
block
|
||||
type="outline"
|
||||
size="large"
|
||||
disabled={isLoading || isSuccess}
|
||||
disabled={isVerifying || isSuccess}
|
||||
onClick={onClickLogout}
|
||||
className="opacity-80 hover:opacity-100 transition"
|
||||
>
|
||||
@@ -132,10 +125,10 @@ const SignInMfaForm = () => {
|
||||
form="sign-in-mfa-form"
|
||||
htmlType="submit"
|
||||
size="large"
|
||||
disabled={isLoading || isSuccess}
|
||||
loading={isLoading || isSuccess}
|
||||
disabled={isVerifying || isSuccess}
|
||||
loading={isVerifying || isSuccess}
|
||||
>
|
||||
{isLoading ? 'Verifying' : isSuccess ? 'Signing in' : 'Verify'}
|
||||
{isVerifying ? 'Verifying' : isSuccess ? 'Signing in' : 'Verify'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,9 +156,9 @@ const SignInMfaForm = () => {
|
||||
)}
|
||||
<li>
|
||||
<Link
|
||||
href="/support/new?subject=Unable+to+sign+in+via+MFA&category=Login_issues"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="/support/new?subject=Unable+to+sign+in+via+MFA&category=Login_issues"
|
||||
className="text-sm transition text-foreground-light hover:text-foreground"
|
||||
>
|
||||
Reach out to us via support
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
import { CheckCircle } from 'lucide-react'
|
||||
import { CheckCircle, Eye, EyeOff } from 'lucide-react'
|
||||
import { useRef, useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import * as yup from 'yup'
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
|
||||
import { useSignUpMutation } from 'data/misc/signup-mutation'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
@@ -14,14 +13,10 @@ import {
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
Form,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
Input,
|
||||
} from 'ui'
|
||||
import PasswordConditionsHelper from './PasswordConditionsHelper'
|
||||
|
||||
const WHITELIST_ERRORS = ['A user with this email already exists']
|
||||
|
||||
const signUpSchema = passwordSchema.shape({
|
||||
email: yup.string().email().required().label('Email'),
|
||||
})
|
||||
@@ -42,10 +37,6 @@ const SignUpForm = () => {
|
||||
setCaptchaToken(null)
|
||||
captchaRef.current?.resetCaptcha()
|
||||
toast.error(`Failed to sign up: ${error.message}`)
|
||||
|
||||
if (!WHITELIST_ERRORS.includes(error.message)) {
|
||||
Sentry.captureMessage('[CRITICAL] Failed to sign up: ' + error.message)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -118,7 +109,7 @@ const SignUpForm = () => {
|
||||
onFocus={() => setShowConditions(true)}
|
||||
actions={
|
||||
<Button
|
||||
icon={passwordHidden ? <IconEye /> : <IconEyeOff />}
|
||||
icon={passwordHidden ? <Eye /> : <EyeOff />}
|
||||
type="default"
|
||||
className="!mr-1"
|
||||
onClick={() => setPasswordHidden((prev) => !prev)}
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import { UPLOAD_FILE_EXTENSIONS } from './SpreadsheetImport.constants'
|
||||
const CHUNK_SIZE = 1024 * 1024 * 0.25 // 0.25MB
|
||||
|
||||
export const parseSpreadsheetText: any = (text: string) => {
|
||||
const columnTypeMap: any = {}
|
||||
const columnTypeMap: Record<any, any> = {}
|
||||
let previewRows: any[] = []
|
||||
return new Promise((resolve) => {
|
||||
Papa.parse(text, {
|
||||
@@ -49,7 +49,7 @@ export const parseSpreadsheet = (
|
||||
let rowCount = 0
|
||||
let previewRows: any[] = []
|
||||
|
||||
const columnTypeMap: any = {}
|
||||
const columnTypeMap: Record<any, any> = {}
|
||||
const errors: any[] = []
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
import { CriticalIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
import { useProjectContext } from './ProjectContext'
|
||||
|
||||
const PauseFailedState = () => {
|
||||
const { project } = useProjectContext()
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="bg-surface-100 border border-overlay rounded-md w-3/4 lg:w-1/2">
|
||||
<div className="space-y-6 pt-6">
|
||||
<div className="flex px-8 space-x-8">
|
||||
<div className="mt-1">
|
||||
<CriticalIcon className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p>Something went wrong while pausing your project</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your project's data is intact, but your project is inaccessible due to the failure
|
||||
while pausing. Please contact support for assistance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-overlay flex items-center justify-end py-4 px-8">
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={`/support/new?category=Database_unresponsive&ref=${project?.ref}&subject=Restoration%20failed%20for%20project`}
|
||||
>
|
||||
Contact support
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PauseFailedState
|
||||
@@ -10,6 +10,7 @@ import ResourceExhaustionWarningBanner from 'components/ui/ResourceExhaustionWar
|
||||
import { useFlag, useSelectedOrganization, useSelectedProject, withAuth } from 'hooks'
|
||||
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
|
||||
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup, cn } from 'ui'
|
||||
import AppLayout from '../AppLayout/AppLayout'
|
||||
import EnableBranchingModal from '../AppLayout/EnableBranchingButton/EnableBranchingModal'
|
||||
import BuildingState from './BuildingState'
|
||||
@@ -17,13 +18,15 @@ import ConnectingState from './ConnectingState'
|
||||
import { LayoutHeader } from './LayoutHeader'
|
||||
import LoadingState from './LoadingState'
|
||||
import NavigationBar from './NavigationBar/NavigationBar'
|
||||
import PauseFailedState from './PauseFailedState'
|
||||
import PausingState from './PausingState'
|
||||
import ProductMenuBar from './ProductMenuBar'
|
||||
import { ProjectContextProvider } from './ProjectContext'
|
||||
import ProjectPausedState from './ProjectPausedState'
|
||||
import RestartingState from './RestartingState'
|
||||
import RestoreFailedState from './RestoreFailedState'
|
||||
import RestoringState from './RestoringState'
|
||||
import { UpgradingState } from './UpgradingState'
|
||||
import { ResizableHandle, ResizablePanelGroup, ResizablePanel, cn } from 'ui'
|
||||
|
||||
// [Joshen] This is temporary while we unblock users from managing their project
|
||||
// if their project is not responding well for any reason. Eventually needs a bit of an overhaul
|
||||
@@ -221,14 +224,17 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
|
||||
const requiresPostgrestConnection = !routesToIgnorePostgrestConnection.includes(router.pathname)
|
||||
const requiresProjectDetails = !routesToIgnoreProjectDetailsRequest.includes(router.pathname)
|
||||
|
||||
const isRestarting = selectedProject?.status === PROJECT_STATUS.RESTARTING
|
||||
const isProjectUpgrading = selectedProject?.status === PROJECT_STATUS.UPGRADING
|
||||
const isProjectRestoring = selectedProject?.status === PROJECT_STATUS.RESTORING
|
||||
const isProjectRestoreFailed = selectedProject?.status === PROJECT_STATUS.RESTORE_FAILED
|
||||
const isProjectBuilding =
|
||||
selectedProject?.status === PROJECT_STATUS.COMING_UP ||
|
||||
selectedProject?.status === PROJECT_STATUS.UNKNOWN
|
||||
const isProjectPausing =
|
||||
selectedProject?.status === PROJECT_STATUS.GOING_DOWN ||
|
||||
selectedProject?.status === PROJECT_STATUS.PAUSING
|
||||
const isProjectPauseFailed = selectedProject?.status === PROJECT_STATUS.PAUSE_FAILED
|
||||
const isProjectOffline = selectedProject?.postgrestStatus === 'OFFLINE'
|
||||
|
||||
useEffect(() => {
|
||||
@@ -239,6 +245,10 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
|
||||
return router.pathname.endsWith('[ref]') ? <LoadingState /> : <Loading />
|
||||
}
|
||||
|
||||
if (isRestarting) {
|
||||
return <RestartingState />
|
||||
}
|
||||
|
||||
if (isProjectUpgrading) {
|
||||
return <UpgradingState />
|
||||
}
|
||||
@@ -247,6 +257,10 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
|
||||
return <PausingState project={selectedProject} />
|
||||
}
|
||||
|
||||
if (isProjectPauseFailed) {
|
||||
return <PauseFailedState />
|
||||
}
|
||||
|
||||
if (requiresPostgrestConnection && isProjectOffline) {
|
||||
return <ConnectingState project={selectedProject} />
|
||||
}
|
||||
@@ -255,6 +269,10 @@ const ContentWrapper = ({ isLoading, isBlocking = true, children }: ContentWrapp
|
||||
return <RestoringState />
|
||||
}
|
||||
|
||||
if (isProjectRestoreFailed) {
|
||||
return <RestoreFailedState />
|
||||
}
|
||||
|
||||
if (requiresDbConnection && isProjectBuilding) {
|
||||
return <BuildingState />
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { ExternalLink, PauseCircle } from 'lucide-react'
|
||||
import { Download, PauseCircle } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useBackupsQuery } from 'data/database/backups-query'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { useBackupDownloadMutation } from 'data/database/backup-download-mutation'
|
||||
import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query'
|
||||
import { useProjectPauseStatusQuery } from 'data/projects/project-pause-status-query'
|
||||
import { useProjectRestoreMutation } from 'data/projects/project-restore-mutation'
|
||||
import { setProjectStatus } from 'data/projects/projects-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import { useCheckPermissions, useSelectedOrganization } from 'hooks'
|
||||
import dayjs from 'dayjs'
|
||||
import { useCheckPermissions, useFlag, useSelectedOrganization } from 'hooks'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
@@ -24,9 +26,11 @@ import {
|
||||
TooltipTrigger_Shadcn_,
|
||||
Tooltip_Shadcn_,
|
||||
} from 'ui'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns'
|
||||
import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
import { useProjectContext } from './ProjectContext'
|
||||
import { RestorePaidPlanProjectNotice } from './RestorePaidPlanProjectNotice'
|
||||
|
||||
export interface ProjectPausedStateProps {
|
||||
product?: string
|
||||
@@ -37,26 +41,36 @@ const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
|
||||
const queryClient = useQueryClient()
|
||||
const { project } = useProjectContext()
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const enforceNinetyDayUnpauseExpiry = useFlag('enforceNinetyDayUnpauseExpiry')
|
||||
|
||||
const orgSlug = selectedOrganization?.slug
|
||||
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug })
|
||||
// const { data: pauseStatus } = useProjectPauseStatusQuery(
|
||||
// { ref },
|
||||
// { enabled: project?.status === PROJECT_STATUS.INACTIVE }
|
||||
// )
|
||||
const {
|
||||
data: pauseStatus,
|
||||
error: pauseStatusError,
|
||||
isError,
|
||||
isSuccess,
|
||||
isLoading,
|
||||
} = useProjectPauseStatusQuery(
|
||||
{ ref },
|
||||
{
|
||||
enabled: project?.status === PROJECT_STATUS.INACTIVE && enforceNinetyDayUnpauseExpiry,
|
||||
}
|
||||
)
|
||||
|
||||
const finalDaysRemainingBeforeRestoreDisabled =
|
||||
pauseStatus?.remaining_days_till_restore_disabled ??
|
||||
pauseStatus?.max_days_till_restore_disabled ??
|
||||
0
|
||||
|
||||
const isFreePlan = subscription?.plan?.id === 'free'
|
||||
const isRestoreDisabled = false // !pauseStatus?.can_restore
|
||||
const isRestoreDisabled = enforceNinetyDayUnpauseExpiry && isSuccess && !pauseStatus.can_restore
|
||||
const latestBackup = pauseStatus?.latest_downloadable_backup_id
|
||||
|
||||
const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery(
|
||||
{ slug: orgSlug },
|
||||
{ enabled: isFreePlan }
|
||||
)
|
||||
const { data: backups } = useBackupsQuery({ projectRef: ref })
|
||||
const sortedBackups = (backups?.backups ?? []).sort(
|
||||
(a, b) => new Date(b.inserted_at).valueOf() - new Date(a.inserted_at).valueOf()
|
||||
)
|
||||
const latestBackup = sortedBackups[0]
|
||||
|
||||
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
|
||||
const [showConfirmRestore, setShowConfirmRestore] = useState(false)
|
||||
@@ -69,6 +83,19 @@ const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
|
||||
},
|
||||
})
|
||||
|
||||
const { mutate: downloadBackup, isLoading: isDownloading } = useBackupDownloadMutation({
|
||||
onSuccess: (res) => {
|
||||
const { fileUrl } = res
|
||||
|
||||
// Trigger browser download by create,trigger and remove tempLink
|
||||
const tempLink = document.createElement('a')
|
||||
tempLink.href = fileUrl
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
document.body.removeChild(tempLink)
|
||||
},
|
||||
})
|
||||
|
||||
const canResumeProject = useCheckPermissions(
|
||||
PermissionAction.INFRA_EXECUTE,
|
||||
'queue_jobs.projects.initialize_or_resume'
|
||||
@@ -88,6 +115,24 @@ const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
|
||||
restoreProject({ ref: project.ref })
|
||||
}
|
||||
|
||||
const onSelectDownloadBackup = () => {
|
||||
if (ref === undefined) return console.error('Project ref is required')
|
||||
if (!latestBackup) return toast.error('No backups available for download')
|
||||
|
||||
downloadBackup({
|
||||
ref,
|
||||
backup: {
|
||||
id: latestBackup,
|
||||
// [Joshen] Just FYI these params aren't required for the download backup request
|
||||
// API types need to be updated
|
||||
project_id: -1,
|
||||
inserted_at: '',
|
||||
isPhysicalBackup: false,
|
||||
status: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
@@ -117,88 +162,95 @@ const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* {isRestoreDisabled ? (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Project cannot be restored through the dashboard
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
This project has been paused for over{' '}
|
||||
<span className="text-foreground">
|
||||
{pauseStatus?.max_days_till_restore_disabled ?? 90} days
|
||||
</span>{' '}
|
||||
and cannot be restored through the dashboard. However, your data remains
|
||||
intact and can be downloaded as a backup.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="flex items-center gap-x-2 mt-3">
|
||||
<Tooltip_Shadcn_>
|
||||
<TooltipTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
disabled={latestBackup === undefined}
|
||||
className="pointer-events-auto"
|
||||
>
|
||||
Download backup
|
||||
</Button>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
{latestBackup === undefined && (
|
||||
<TooltipContent_Shadcn_ side="bottom">
|
||||
No backups available
|
||||
</TooltipContent_Shadcn_>
|
||||
)}
|
||||
</Tooltip_Shadcn_>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : isFreePlan ? (
|
||||
{enforceNinetyDayUnpauseExpiry && (
|
||||
<>
|
||||
<p className="text-sm text-foreground-light text-center">
|
||||
To prevent future pauses, consider upgrading to Pro.
|
||||
</p>
|
||||
<Alert_Shadcn_>
|
||||
<AlertTitle_Shadcn_>
|
||||
Project can be restored through the dashboard within the next{' '}
|
||||
{pauseStatus.remaining_days_till_restore_disabled} day
|
||||
{(pauseStatus?.remaining_days_till_restore_disabled ?? 0) > 1 ? 's' : ''}
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Free projects cannot be restored through the dashboard if they are paused
|
||||
for more than{' '}
|
||||
<span className="text-foreground">
|
||||
{pauseStatus.max_days_till_restore_disabled} days
|
||||
</span>
|
||||
. However, your database backup will still be available for download.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
{isLoading && <GenericSkeletonLoader />}
|
||||
{isError && (
|
||||
<AlertError
|
||||
error={pauseStatusError}
|
||||
subject="Failed to retrieve pause status"
|
||||
/>
|
||||
)}
|
||||
{isSuccess && (
|
||||
<>
|
||||
{isRestoreDisabled ? (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Project cannot be restored through the dashboard
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
This project has been paused for over{' '}
|
||||
<span className="text-foreground">
|
||||
{pauseStatus?.max_days_till_restore_disabled ?? 90} days
|
||||
</span>{' '}
|
||||
and cannot be restored through the dashboard. However, your data
|
||||
remains intact and can be downloaded as a backup.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="flex items-center gap-x-2 mt-3">
|
||||
<Tooltip_Shadcn_>
|
||||
<TooltipTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<Download />}
|
||||
loading={isDownloading}
|
||||
disabled={!latestBackup}
|
||||
className="pointer-events-auto"
|
||||
onClick={() => onSelectDownloadBackup()}
|
||||
>
|
||||
Download backup
|
||||
</Button>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
{!latestBackup && (
|
||||
<TooltipContent_Shadcn_ side="bottom">
|
||||
No backups available, please reach out via support for
|
||||
assistance
|
||||
</TooltipContent_Shadcn_>
|
||||
)}
|
||||
</Tooltip_Shadcn_>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : isFreePlan ? (
|
||||
<>
|
||||
<p className="text-sm text-foreground-light text-center">
|
||||
To prevent future pauses, consider upgrading to Pro.
|
||||
</p>
|
||||
<Alert_Shadcn_>
|
||||
<AlertTitle_Shadcn_>
|
||||
Project can be restored through the dashboard within the next{' '}
|
||||
{finalDaysRemainingBeforeRestoreDisabled} day
|
||||
{finalDaysRemainingBeforeRestoreDisabled > 1 ? 's' : ''}
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
Free projects cannot be restored through the dashboard if they are
|
||||
paused for more than{' '}
|
||||
<span className="text-foreground">
|
||||
{pauseStatus?.max_days_till_restore_disabled} days
|
||||
</span>
|
||||
. The latest that your project can be restored is by{' '}
|
||||
<span className="text-foreground">
|
||||
{dayjs()
|
||||
.utc()
|
||||
.add(pauseStatus.max_days_till_restore_disabled ?? 0, 'day')
|
||||
.format('DD MMM YYYY')}
|
||||
</span>
|
||||
. However, your database backup will still be available for download
|
||||
thereafter.
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</>
|
||||
) : (
|
||||
<RestorePaidPlanProjectNotice />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : null} */}
|
||||
|
||||
{!isFreePlan && (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Project will count towards compute usage once restored
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
For every hour your instance is active, we will bill you based on the instance
|
||||
size of your project.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-3">
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/org-based-billing#usage-based-billing-for-compute"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
More information
|
||||
</a>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
|
||||
{!enforceNinetyDayUnpauseExpiry && !isFreePlan && <RestorePaidPlanProjectNotice />}
|
||||
</div>
|
||||
|
||||
{!isRestoreDisabled && (
|
||||
{(!enforceNinetyDayUnpauseExpiry || (isSuccess && !isRestoreDisabled)) && (
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
<Tooltip_Shadcn_>
|
||||
<TooltipTrigger_Shadcn_ asChild>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useParams } from 'common'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
|
||||
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
|
||||
import { PROJECT_STATUS } from 'lib/constants'
|
||||
|
||||
const RestartingState = () => {
|
||||
const { ref } = useParams()
|
||||
useProjectDetailQuery(
|
||||
{ ref },
|
||||
{
|
||||
// setting a refetch interval here will cause the `useSelectedProject()` in `ProjectLayout.tsx` to
|
||||
// rerender every 4 seconds while the project is restarting. Once restarting is complete, it will
|
||||
// no longer show this state.
|
||||
refetchInterval(data) {
|
||||
return data?.status !== PROJECT_STATUS.ACTIVE_HEALTHY ? 4000 : false
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="bg-surface-100 border border-overlay rounded-md w-3/4 lg:w-1/2">
|
||||
<div className="space-y-6 py-6">
|
||||
<div className="flex px-8 space-x-8">
|
||||
<div className="mt-1">
|
||||
<Loader2 className="animate-spin" size={18} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p>Restarting...</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Restarting can take a few minutes. Your project will be offline while it restarts.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RestartingState
|
||||
@@ -0,0 +1,42 @@
|
||||
import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
import { CriticalIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
import { useProjectContext } from './ProjectContext'
|
||||
|
||||
const RestoreFailedState = () => {
|
||||
const { project } = useProjectContext()
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="bg-surface-100 border border-overlay rounded-md w-3/4 lg:w-1/2">
|
||||
<div className="space-y-6 pt-6">
|
||||
<div className="flex px-8 space-x-8">
|
||||
<div className="mt-1">
|
||||
<CriticalIcon className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p>Something went wrong while restoring your project</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Your project's data is intact, but your project is inaccessible due to the
|
||||
restoration failure. Please contact support for assistance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-overlay flex items-center justify-end py-4 px-8">
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={`/support/new?category=Database_unresponsive&ref=${project?.ref}&subject=Restoration%20failed%20for%20project`}
|
||||
>
|
||||
Contact support
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RestoreFailedState
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button } from 'ui'
|
||||
import { WarningIcon } from 'ui-patterns/Icons/StatusIcons'
|
||||
|
||||
export const RestorePaidPlanProjectNotice = () => {
|
||||
return (
|
||||
<Alert_Shadcn_>
|
||||
<WarningIcon />
|
||||
<AlertTitle_Shadcn_>
|
||||
Project will count towards compute usage once restored
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>
|
||||
For every hour your instance is active, we will bill you based on the instance size of your
|
||||
project.
|
||||
</AlertDescription_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="mt-3">
|
||||
<Button asChild type="default" icon={<ExternalLink />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/platform/org-based-billing#usage-based-billing-for-compute"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
More information
|
||||
</a>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Button, IconAlertCircle, IconCheckCircle, IconLoader } from 'ui'
|
||||
import { Button, IconCheckCircle, IconLoader } from 'ui'
|
||||
|
||||
import { projectKeys } from 'data/projects/keys'
|
||||
import { invalidateProjectDetailsQuery } from 'data/projects/project-detail-query'
|
||||
import { getWithTimeout } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
@@ -16,7 +16,6 @@ const RestoringState = () => {
|
||||
const checkServerInterval = useRef<number>()
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isFailed, setIsFailed] = useState(false)
|
||||
const [isCompleted, setIsCompleted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -32,12 +31,11 @@ const RestoringState = () => {
|
||||
})
|
||||
if (projectStatus && !projectStatus.error) {
|
||||
const { status } = projectStatus
|
||||
if (status === PROJECT_STATUS.RESTORATION_FAILED) {
|
||||
clearInterval(checkServerInterval.current)
|
||||
setIsFailed(true)
|
||||
} else if (status === PROJECT_STATUS.ACTIVE_HEALTHY) {
|
||||
if (status === PROJECT_STATUS.ACTIVE_HEALTHY) {
|
||||
clearInterval(checkServerInterval.current)
|
||||
setIsCompleted(true)
|
||||
} else {
|
||||
queryClient.invalidateQueries(projectKeys.detail(ref))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,32 +69,6 @@ const RestoringState = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : isFailed ? (
|
||||
<div className="space-y-6 pt-6">
|
||||
<div className="flex px-8 space-x-8">
|
||||
<div className="mt-1">
|
||||
<IconAlertCircle size={18} strokeWidth={2} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p>Something went wrong while restoring your project</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Our engineers have already been notified of this, do hang tight while we are
|
||||
investigating into the issue.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{isFailed && (
|
||||
<div className="border-t border-overlay flex items-center justify-end py-4 px-8">
|
||||
<Button asChild type="default">
|
||||
<Link
|
||||
href={`/support/new?category=Database_unresponsive&ref=${project?.ref}&subject=Restoration%20failed%20for%20project`}
|
||||
>
|
||||
Contact support
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6 py-6">
|
||||
<div className="flex px-8 space-x-8">
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { handleError, post } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
|
||||
const WHITELIST_ERRORS = ['email must be an email']
|
||||
|
||||
export type ResetPasswordVariables = {
|
||||
email: string
|
||||
hcaptchaToken: string | null
|
||||
@@ -42,6 +45,9 @@ export const useResetPasswordMutation = ({
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
if (!WHITELIST_ERRORS.some((error) => data.message.includes(error))) {
|
||||
Sentry.captureMessage('[CRITICAL] Failed to send reset password email: ' + data.message)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
import { handleError, post } from 'data/fetchers'
|
||||
import type { ResponseError } from 'types'
|
||||
|
||||
const WHITELIST_ERRORS = [
|
||||
'A user with this email already exists',
|
||||
'Password should contain at least one character of each',
|
||||
'You attempted to send email to an inactive recipient',
|
||||
'email must be an email',
|
||||
]
|
||||
|
||||
export type SignUpVariables = {
|
||||
email: string
|
||||
password: string
|
||||
@@ -38,6 +46,9 @@ export const useSignUpMutation = ({
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
if (!WHITELIST_ERRORS.some((error) => data.message.includes(error))) {
|
||||
Sentry.captureMessage('[CRITICAL] Failed to sign up: ' + data.message)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
})
|
||||
|
||||
@@ -5,8 +5,7 @@ import { get, handleError } from 'data/fetchers'
|
||||
import { components } from 'api-types'
|
||||
|
||||
export type OrganizationPaymentMethodsVariables = { slug?: string }
|
||||
export type OrganizationPaymentMethodsResponse = components['schemas']['PaymentsResponseV2']
|
||||
export type OrganizationPaymentMethod = components['schemas']['PaymentV2']
|
||||
export type OrganizationPaymentMethod = components['schemas']['Payment']
|
||||
|
||||
export async function getOrganizationPaymentMethods(
|
||||
{ slug }: OrganizationPaymentMethodsVariables,
|
||||
@@ -25,11 +24,9 @@ export async function getOrganizationPaymentMethods(
|
||||
},
|
||||
signal,
|
||||
})
|
||||
if (error) handleError(error)
|
||||
|
||||
// Due to API versioning, this is not correctly recognized until the old endpoint is removed
|
||||
// @ts-ignore
|
||||
return data as OrganizationPaymentMethodsResponse
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
}
|
||||
|
||||
export type OrganizationPaymentMethodsData = Awaited<
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import type { AuthMFAVerifyResponse, MFAChallengeAndVerifyParams } from '@supabase/supabase-js'
|
||||
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
|
||||
import { UseMutationOptions, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { auth } from 'lib/gotrue'
|
||||
import { profileKeys } from './keys'
|
||||
|
||||
const WHITELIST_ERRORS = ['Invalid TOTP code entered']
|
||||
|
||||
interface MFAChallengeAndVerifyVariables extends MFAChallengeAndVerifyParams {
|
||||
refreshFactors?: boolean
|
||||
}
|
||||
@@ -18,6 +23,7 @@ type CustomMFAVerifyError = NonNullable<AuthMFAVerifyResponse['error']>
|
||||
|
||||
export const useMfaChallengeAndVerifyMutation = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
...options
|
||||
}: Omit<
|
||||
UseMutationOptions<CustomMFAVerifyResponse, CustomMFAVerifyError, MFAChallengeAndVerifyVariables>,
|
||||
@@ -42,6 +48,16 @@ export const useMfaChallengeAndVerifyMutation = ({
|
||||
|
||||
await onSuccess?.(data, variables, context)
|
||||
},
|
||||
async onError(data, variables, context) {
|
||||
if (onError === undefined) {
|
||||
toast.error(`Failed to sign in: ${data.message}`)
|
||||
} else {
|
||||
onError(data, variables, context)
|
||||
}
|
||||
if (!WHITELIST_ERRORS.some((error) => data.message.includes(error))) {
|
||||
Sentry.captureMessage('[CRITICAL] Failed to sign in via MFA: ' + data.message)
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -10,13 +10,14 @@ export type ProjectDetailVariables = { ref?: string }
|
||||
export type ProjectMinimal = components['schemas']['ProjectInfo']
|
||||
export type ProjectDetail = components['schemas']['ProjectDetailResponse']
|
||||
|
||||
export interface Project extends ProjectDetail {
|
||||
export interface Project extends Omit<ProjectDetail, 'status'> {
|
||||
/**
|
||||
* postgrestStatus is available on client side only.
|
||||
* We use this status to check if a project instance is HEALTHY or not
|
||||
* If not we will show ConnectingState and run a polling until it's back online
|
||||
*/
|
||||
postgrestStatus?: 'ONLINE' | 'OFFLINE'
|
||||
status: components['schemas']['ResourceWithServicesStatusResponse']['status']
|
||||
}
|
||||
|
||||
export async function getProjectDetail({ ref }: ProjectDetailVariables, signal?: AbortSignal) {
|
||||
|
||||
@@ -11,13 +11,15 @@ export type ProjectsVariables = {
|
||||
ref?: string
|
||||
}
|
||||
|
||||
export type ProjectInfo = components['schemas']['ProjectInfo']
|
||||
export type ProjectInfo = components['schemas']['ProjectInfo'] & {
|
||||
status: components['schemas']['ResourceWithServicesStatusResponse']['status']
|
||||
}
|
||||
|
||||
export async function getProjects(signal?: AbortSignal) {
|
||||
const { data, error } = await get('/platform/projects', { signal })
|
||||
|
||||
if (error) handleError(error)
|
||||
return data
|
||||
return data as ProjectInfo[]
|
||||
}
|
||||
|
||||
export type ProjectsData = Awaited<ReturnType<typeof getProjects>>
|
||||
|
||||
@@ -69,7 +69,7 @@ export const PROVIDERS = {
|
||||
} as const
|
||||
|
||||
export const PROJECT_STATUS: {
|
||||
[key: string]: components['schemas']['ProjectDetailResponse']['status']
|
||||
[key: string]: components['schemas']['ResourceWithServicesStatusResponse']['status']
|
||||
} = {
|
||||
INACTIVE: 'INACTIVE',
|
||||
ACTIVE_HEALTHY: 'ACTIVE_HEALTHY',
|
||||
@@ -79,12 +79,12 @@ export const PROJECT_STATUS: {
|
||||
GOING_DOWN: 'GOING_DOWN',
|
||||
INIT_FAILED: 'INIT_FAILED',
|
||||
REMOVED: 'REMOVED',
|
||||
RESTARTING: 'RESTARTING',
|
||||
RESTORING: 'RESTORING',
|
||||
RESTORE_FAILED: 'RESTORE_FAILED',
|
||||
UPGRADING: 'UPGRADING',
|
||||
// @ts-ignore [Joshen] API codegen seems to be wrong here, pausing is still a valid status
|
||||
PAUSING: 'PAUSING',
|
||||
// @ts-ignore [Joshen] This is no longer part of the project status enum, but leaving here for now just in case
|
||||
RESTORATION_FAILED: 'RESTORATION_FAILED',
|
||||
PAUSE_FAILED: 'PAUSE_FAILED',
|
||||
}
|
||||
|
||||
export const DEFAULT_MINIMUM_PASSWORD_STRENGTH = 4
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { PopoverSeparator } from '@ui/components/shadcn/ui/popover'
|
||||
import { components } from 'api-types'
|
||||
import { useParams } from 'common'
|
||||
import {
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
ProjectCreateVariables,
|
||||
useProjectCreateMutation,
|
||||
} from 'data/projects/project-create-mutation'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
import generator from 'generate-password-browser'
|
||||
import { useCheckPermissions, useFlag, withAuth } from 'hooks'
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
CloudProvider,
|
||||
DEFAULT_MINIMUM_PASSWORD_STRENGTH,
|
||||
DEFAULT_PROVIDER,
|
||||
PROJECT_STATUS,
|
||||
PROVIDERS,
|
||||
} from 'lib/constants'
|
||||
import { passwordStrength } from 'lib/helpers'
|
||||
@@ -59,10 +62,8 @@ import {
|
||||
} from 'ui'
|
||||
import { Input } from 'ui-patterns/DataInputs/Input'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
import { z } from 'zod'
|
||||
import { InfoTooltip } from 'ui-patterns/info-tooltip'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { PopoverSeparator } from '@ui/components/shadcn/ui/popover'
|
||||
import { z } from 'zod'
|
||||
|
||||
type DesiredInstanceSize = components['schemas']['DesiredInstanceSize']
|
||||
|
||||
@@ -173,7 +174,8 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const { data: allProjects } = useProjectsQuery({})
|
||||
const organizationProjects =
|
||||
allProjects?.filter(
|
||||
(project) => project.organization_id === currentOrg?.id && project.status !== 'paused'
|
||||
(project) =>
|
||||
project.organization_id === currentOrg?.id && project.status !== PROJECT_STATUS.INACTIVE
|
||||
) ?? []
|
||||
|
||||
const { data: orgSubscription } = useOrgSubscriptionQuery({
|
||||
|
||||
Vendored
+16
-166
@@ -157,8 +157,6 @@ export interface paths {
|
||||
get: operations['CustomerController_getCustomer']
|
||||
/** Updates the billing customer */
|
||||
put: operations['updateCustomerV2']
|
||||
/** Updates the Stripe customer */
|
||||
patch: operations['CustomerController_updateCustomer']
|
||||
}
|
||||
'/platform/organizations/{slug}/roles': {
|
||||
/** Gets the given organization's roles */
|
||||
@@ -267,7 +265,7 @@ export interface paths {
|
||||
}
|
||||
'/platform/organizations/{slug}/payments': {
|
||||
/** Gets Stripe payment methods for the given organization */
|
||||
get: operations['PaymentsController_getPaymentMethods']
|
||||
get: operations['getPaymentMethodsV2']
|
||||
/** Detach payment method with the given card ID */
|
||||
delete: operations['PaymentsController_detachPaymentMethod']
|
||||
}
|
||||
@@ -2582,51 +2580,11 @@ export interface components {
|
||||
email: string
|
||||
address: components['schemas']['CustomerBillingAddress'] | null
|
||||
balance: number
|
||||
invoice_settings: Record<string, never>
|
||||
billing_via_partner: boolean
|
||||
}
|
||||
BillingCustomerUpdateBody: {
|
||||
address?: components['schemas']['CustomerBillingAddress']
|
||||
}
|
||||
CustomerUpdateResponse: {
|
||||
id: string
|
||||
object: string
|
||||
address: Record<string, never>
|
||||
balance: number
|
||||
cash_balance?: Record<string, never>
|
||||
created: number
|
||||
currency: string
|
||||
default_currency?: string
|
||||
default_source: string
|
||||
delinquent: boolean
|
||||
description: string
|
||||
discount: Record<string, never>
|
||||
email: string
|
||||
invoice_credit_balance?: Record<string, never>
|
||||
invoice_prefix: string
|
||||
invoice_settings: Record<string, never>
|
||||
livemode: boolean
|
||||
metadata: Record<string, never>
|
||||
name: string
|
||||
next_invoice_sequence?: number
|
||||
phone: string
|
||||
preferred_locales: string[]
|
||||
shipping: Record<string, never>
|
||||
sources?: Record<string, never>
|
||||
subscriptions?: Record<string, never>
|
||||
tax?: Record<string, never>
|
||||
tax_exempt?: string
|
||||
tax_ids?: Record<string, never>
|
||||
test_clock?: Record<string, never>
|
||||
lastResponse: {
|
||||
headers?: Record<string, never>
|
||||
requestId?: string
|
||||
statusCode?: number
|
||||
apiVersion?: string
|
||||
idempotencyKey?: string
|
||||
stripeAccount?: string
|
||||
}
|
||||
}
|
||||
OrganizationRole: {
|
||||
id: number
|
||||
name: string
|
||||
@@ -2819,70 +2777,16 @@ export interface components {
|
||||
exp_year: number
|
||||
last4: string
|
||||
}
|
||||
PaymentV2: {
|
||||
Payment: {
|
||||
id: string
|
||||
card?: components['schemas']['PaymentMethodCard']
|
||||
created: number
|
||||
type: string
|
||||
is_default: boolean
|
||||
}
|
||||
PaymentsResponseV2: {
|
||||
defaultPaymentMethodId: string | null
|
||||
data: components['schemas']['PaymentV2'][]
|
||||
}
|
||||
Payment: {
|
||||
id: string
|
||||
object: string
|
||||
acss_debit?: Record<string, never>
|
||||
affirm?: Record<string, never>
|
||||
afterpay_clearpay?: Record<string, never>
|
||||
alipay?: Record<string, never>
|
||||
au_becs_debit?: Record<string, never>
|
||||
bacs_debit?: Record<string, never>
|
||||
bancontact?: Record<string, never>
|
||||
billing_details: Record<string, never>
|
||||
blik?: Record<string, never>
|
||||
boleto?: Record<string, never>
|
||||
card?: Record<string, never>
|
||||
card_present?: Record<string, never>
|
||||
created: number
|
||||
customer: Record<string, never>
|
||||
customer_balance?: Record<string, never>
|
||||
eps?: Record<string, never>
|
||||
fpx?: Record<string, never>
|
||||
giropay?: Record<string, never>
|
||||
grabpay?: Record<string, never>
|
||||
ideal?: Record<string, never>
|
||||
interac_present?: Record<string, never>
|
||||
klarna?: Record<string, never>
|
||||
konbini?: Record<string, never>
|
||||
link?: Record<string, never>
|
||||
livemode: boolean
|
||||
metadata: Record<string, never>
|
||||
oxxo?: Record<string, never>
|
||||
p24?: Record<string, never>
|
||||
paynow?: Record<string, never>
|
||||
promptpay?: Record<string, never>
|
||||
radar_options?: Record<string, never>
|
||||
sepa_debit?: Record<string, never>
|
||||
sofort?: Record<string, never>
|
||||
type: string
|
||||
us_bank_account?: Record<string, never>
|
||||
wechat_pay?: Record<string, never>
|
||||
}
|
||||
PaymentsResponse: {
|
||||
object: string
|
||||
defaultPaymentMethodId: string | null
|
||||
data: components['schemas']['Payment'][]
|
||||
has_more: boolean
|
||||
url: string
|
||||
lastResponse: {
|
||||
headers?: Record<string, never>
|
||||
requestId?: string
|
||||
statusCode?: number
|
||||
apiVersion?: string
|
||||
idempotencyKey?: string
|
||||
stripeAccount?: string
|
||||
}
|
||||
}
|
||||
DetachPaymentMethodBody: {
|
||||
card_id: string
|
||||
@@ -2894,37 +2798,8 @@ export interface components {
|
||||
hcaptchaToken: string
|
||||
}
|
||||
SetupIntentResponse: {
|
||||
id: string
|
||||
object: string
|
||||
application: Record<string, never>
|
||||
attach_to_self?: boolean
|
||||
cancellation_reason: string
|
||||
client_secret: string
|
||||
created: number
|
||||
customer: Record<string, never>
|
||||
description: string
|
||||
flow_directions: Record<string, never>
|
||||
last_setup_error: Record<string, never>
|
||||
latest_attempt: Record<string, never>
|
||||
livemode: boolean
|
||||
mandate: Record<string, never>
|
||||
metadata: Record<string, never>
|
||||
next_action: Record<string, never>
|
||||
on_behalf_of: Record<string, never>
|
||||
payment_method: Record<string, never>
|
||||
payment_method_options: Record<string, never>
|
||||
payment_method_types: string[]
|
||||
single_use_mandate: Record<string, never>
|
||||
status: string
|
||||
usage: string
|
||||
lastResponse: {
|
||||
headers?: Record<string, never>
|
||||
requestId?: string
|
||||
statusCode?: number
|
||||
apiVersion?: string
|
||||
idempotencyKey?: string
|
||||
stripeAccount?: string
|
||||
}
|
||||
}
|
||||
/** @enum {string} */
|
||||
BillingPlanId: 'free' | 'pro' | 'team' | 'enterprise'
|
||||
@@ -2978,12 +2853,6 @@ export interface components {
|
||||
name: string
|
||||
ref: string
|
||||
}
|
||||
PaymentMethodCardDetails: {
|
||||
last_4_digits: string
|
||||
brand: string
|
||||
expiry_month: number
|
||||
expiry_year: number
|
||||
}
|
||||
ScheduledPlanChange: {
|
||||
target_plan: components['schemas']['BillingPlanId']
|
||||
/** Format: date-time */
|
||||
@@ -3000,8 +2869,6 @@ export interface components {
|
||||
addons: components['schemas']['BillingSubscriptionAddon'][]
|
||||
project_addons: components['schemas']['BillingProjectAddonResponse'][]
|
||||
payment_method_type: string
|
||||
payment_method_id?: string
|
||||
payment_method_card_details?: components['schemas']['PaymentMethodCardDetails']
|
||||
billing_via_partner: boolean
|
||||
/** @enum {string} */
|
||||
billing_partner: 'fly' | 'aws'
|
||||
@@ -3947,9 +3814,10 @@ export interface components {
|
||||
}
|
||||
Buffer: Record<string, never>
|
||||
PauseStatusResponse: {
|
||||
max_days_till_restore_disabled: number | null
|
||||
max_days_till_restore_disabled: number
|
||||
remaining_days_till_restore_disabled: number | null
|
||||
can_restore: boolean | null
|
||||
can_restore: boolean
|
||||
latest_downloadable_backup_id: number | null
|
||||
}
|
||||
ResizeBody: {
|
||||
volume_size_gb: number
|
||||
@@ -4015,16 +3883,20 @@ export interface components {
|
||||
region: string
|
||||
/** @enum {string} */
|
||||
status:
|
||||
| 'INACTIVE'
|
||||
| 'ACTIVE_HEALTHY'
|
||||
| 'ACTIVE_UNHEALTHY'
|
||||
| 'COMING_UP'
|
||||
| 'UNKNOWN'
|
||||
| 'GOING_DOWN'
|
||||
| 'INACTIVE'
|
||||
| 'INIT_FAILED'
|
||||
| 'REMOVED'
|
||||
| 'RESTORING'
|
||||
| 'RESTARTING'
|
||||
| 'UNKNOWN'
|
||||
| 'UPGRADING'
|
||||
| 'PAUSING'
|
||||
| 'RESTORING'
|
||||
| 'RESTORE_FAILED'
|
||||
| 'PAUSE_FAILED'
|
||||
subscription_id: string
|
||||
connectionString: string
|
||||
kpsVersion?: string
|
||||
@@ -5676,6 +5548,7 @@ export interface components {
|
||||
default?: Record<string, never> | number | string | boolean
|
||||
name?: string
|
||||
names?: string[]
|
||||
array?: boolean
|
||||
}
|
||||
AttributeMapping: {
|
||||
keys: {
|
||||
@@ -6904,7 +6777,7 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
200: {
|
||||
204: {
|
||||
content: never
|
||||
}
|
||||
403: {
|
||||
@@ -6916,29 +6789,6 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Updates the Stripe customer */
|
||||
CustomerController_updateCustomer: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description Organization slug */
|
||||
slug: string
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['CustomerUpdateResponse']
|
||||
}
|
||||
}
|
||||
403: {
|
||||
content: never
|
||||
}
|
||||
/** @description Failed to update the Stripe customer */
|
||||
500: {
|
||||
content: never
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Gets the given organization's roles */
|
||||
OrganizationRolesController_getAllRolesV2: {
|
||||
parameters: {
|
||||
@@ -7573,7 +7423,7 @@ export interface operations {
|
||||
}
|
||||
}
|
||||
/** Gets Stripe payment methods for the given organization */
|
||||
PaymentsController_getPaymentMethods: {
|
||||
getPaymentMethodsV2: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description Organization slug */
|
||||
|
||||
Reference in new issue
Block a user