Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/docs/content/guides/auth/quickstarts/react-native.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ hideToc: true

Create a helper file `lib/supabase.ts` that exports a Supabase client using your Project URL and key.

Create a `.env` file and populate with your Supabase connection variables:
Rename `.env.example` to `.env` and populate with your Supabase connection variables:

<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="publishable" />
Expand All @@ -90,7 +90,7 @@ hideToc: true
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Create a login component">

Let's set up a React Native component to manage logins and sign ups.
Create a React Native component to manage logins and sign ups. The app later uses the [`getClaims`](/docs/reference/javascript/auth-getclaims) method in `App.tsx` to validate the local JWT before showing the signed-in user.

</StepHikeCompact.Details>

Expand Down
171 changes: 11 additions & 160 deletions apps/docs/content/guides/auth/quickstarts/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ hideToc: true

<StepHikeCompact.Code>

```text name=.env.local
VITE_SUPABASE_URL=your-project-url
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=sb_publishable_... or anon key
```
<$CodeSample
path="/auth/quickstarts/react/.env.example"
lines={[[1, -1]]}
meta="name=.env.local"
/>
<$Partial path="api_settings_steps.mdx" variables={{ "framework": "react", "tab": "frameworks" }} />

</StepHikeCompact.Code>
Expand All @@ -90,166 +91,16 @@ hideToc: true

In `App.jsx`, create a Supabase client using your Project URL and key.

You can configure the Auth component to display whenever there is no session inside `supabase.auth.getSession()`
The code uses the [`getClaims`](/docs/reference/javascript/auth-getclaims) method in `App.jsx` to validate the local JWT before showing the signed-in user.

</StepHikeCompact.Details>
<StepHikeCompact.Code>

```jsx name=src/App.jsx
import "./index.css";
import { useState, useEffect } from "react";
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(import.meta.env.VITE_SUPABASE_URL, import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY);

export default function App() {
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState("");
const [session, setSession] = useState(null);

// Check URL params on initial render
const params = new URLSearchParams(window.location.search);
const hasTokenHash = params.get("token_hash");

const [verifying, setVerifying] = useState(!!hasTokenHash);
const [authError, setAuthError] = useState(null);
const [authSuccess, setAuthSuccess] = useState(false);

useEffect(() => {
// Check if we have token_hash in URL (magic link callback)
const params = new URLSearchParams(window.location.search);
const token_hash = params.get("token_hash");
const type = params.get("type");

if (token_hash) {
// Verify the OTP token
supabase.auth.verifyOtp({
token_hash,
type: type || "email",
}).then(({ error }) => {
if (error) {
setAuthError(error.message);
} else {
setAuthSuccess(true);
// Clear URL params
window.history.replaceState({}, document.title, "/");
}
setVerifying(false);
});
}

// Check for existing session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});

// Listen for auth changes
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});

return () => subscription.unsubscribe();
}, []);

const handleLogin = async (event) => {
event.preventDefault();
setLoading(true);
const { error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: window.location.origin,
}
});
if (error) {
alert(error.error_description || error.message);
} else {
alert("Check your email for the login link!");
}
setLoading(false);
};

const handleLogout = async () => {
await supabase.auth.signOut();
setSession(null);
};

// Show verification state
if (verifying) {
return (
<div>
<h1>Authentication</h1>
<p>Confirming your magic link...</p>
<p>Loading...</p>
</div>
);
}

// Show auth error
if (authError) {
return (
<div>
<h1>Authentication</h1>
<p>✗ Authentication failed</p>
<p>{authError}</p>
<button
onClick={() => {
setAuthError(null);
window.history.replaceState({}, document.title, "/");
}}
>
Return to login
</button>
</div>
);
}

// Show auth success (briefly before session loads)
if (authSuccess && !session) {
return (
<div>
<h1>Authentication</h1>
<p>✓ Authentication successful!</p>
<p>Loading your account...</p>
</div>
);
}

// If user is logged in, show welcome screen
if (session) {
return (
<div>
<h1>Welcome!</h1>
<p>You are logged in as: {session.user.email}</p>
<button onClick={handleLogout}>
Sign Out
</button>
</div>
);
}

// Show login form
return (
<div>
<h1>Supabase + React</h1>
<p>Sign in via magic link with your email below</p>
<form onSubmit={handleLogin}>
<input
type="email"
placeholder="Your email"
value={email}
required={true}
onChange={(e) => setEmail(e.target.value)}
/>
<button disabled={loading}>
{loading ? <span>Loading</span> : <span>Send magic link</span>}
</button>
</form>
</div>
);
}
```
<$CodeSample
path="/auth/quickstarts/react/src/App.jsx"
lines={[[1, -1]]}
meta="name=src/App.jsx"
/>

</StepHikeCompact.Code>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ lines={[[1, -1]]}
meta="name=src/environments/environment.ts"
/>

Now you have the API credentials in place, create a `SupabaseService` with `ng g s supabase` and add the following code to initialize the Supabase client and implement functions to communicate with the Supabase API.
With the API credentials in place, create a `SupabaseService` with `ng g s supabase` and add the following code to initialize the Supabase client and implement functions to communicate with the Supabase API.

This uses the [`getUser`](/docs/reference/javascript/auth-getuser) method to get the current user details if there is an existing session. This method performs a network request to the Supabase Auth server.

<$CodeSample
path="/user-management/angular-user-management/src/app/supabase.service.ts"
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/guides/platform/ssl-enforcement.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ Your Supabase project supports connecting to the Postgres DB without SSL enabled

Disabling SSL enforcement only applies to connections to Postgres and Supavisor ("Connection Pooler"); all HTTP APIs offered by Supabase (e.g., PostgREST, Storage, Auth) automatically enforce SSL on all incoming connections.

<Admonition type="note">
<Admonition type="caution">

Projects need to be at least on Postgres 13.3.0 to enable SSL enforcement. You can find the Postgres version of your project in the [Infrastructure Settings page](/dashboard/project/_/settings/infrastructure). If your project is on an older version, you will need to [upgrade](/docs/guides/platform/migrating-and-upgrading-projects#upgrade-your-project) to use this feature.
Applying or updating SSL enforcement triggers a fast database reboot. On small projects this usually completes in a few seconds, but larger databases may see a longer interruption.

</Admonition>

Expand Down
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Hardik Maheshwari
Haritabh Gupta
Haydn Maley
Hieu Pham
Hunter Luckow
Ignacio Dobronich
Illia Basalaiev
Inian P
Expand Down
2 changes: 2 additions & 0 deletions apps/studio/.env
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ NEXT_PUBLIC_SUPABASE_URL=https://xguihxuzqibwxjnimxev.supabase.co

DOCKER_SOCKET_LOCATION=/var/run/docker.sock

# Required to manage edge-functions via dashboard on Self-Hosting
# EDGE_FUNCTIONS_MANAGEMENT_FOLDER=/path-where-edge-functions-are-mounted
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
import { Eye } from 'lucide-react'
import { useOperationQueueActions } from 'components/grid/hooks/useOperationQueueActions'
import { useOperationQueueShortcuts } from 'components/grid/hooks/useOperationQueueShortcuts'
import { useIsQueueOperationsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { AnimatePresence, motion } from 'framer-motion'
import { Eye } from 'lucide-react'
import { createPortal } from 'react-dom'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { Button } from 'ui'

import {
useOperationQueueShortcuts,
getModKey,
} from 'components/grid/hooks/useOperationQueueShortcuts'
import { useIsQueueOperationsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useOperationQueueActions } from 'components/grid/hooks/useOperationQueueActions'
import { getModKeyLabel } from '@/lib/helpers'

export const SaveQueueActionBar = () => {
const modKey = getModKeyLabel()
const snap = useTableEditorStateSnapshot()
const isQueueOperationsEnabled = useIsQueueOperationsEnabled()
const { handleSave } = useOperationQueueActions()

useOperationQueueShortcuts()

const operationCount = snap.operationQueue.operations.length
const isSaving = snap.operationQueue.status === 'saving'
const isOperationQueuePanelOpen = snap.sidePanel?.type === 'operation-queue'

const isVisible =
isQueueOperationsEnabled && snap.hasPendingOperations && !isOperationQueuePanelOpen

useOperationQueueShortcuts({
enabled: isQueueOperationsEnabled && snap.hasPendingOperations,
onSave: handleSave,
onTogglePanel: () => snap.onViewOperationQueue(),
isSaving,
hasOperations: operationCount > 0,
})

const modKey = getModKey()

const content = (
<AnimatePresence>
{isVisible && (
Expand All @@ -49,7 +40,7 @@ export const SaveQueueActionBar = () => {
</span>
<div className="flex items-center gap-3">
<button
onClick={() => snap.onViewOperationQueue()}
onClick={() => snap.toggleViewOperationQueue()}
className="text-foreground-light hover:text-foreground transition-colors flex items-center"
aria-label="View Details"
>
Expand All @@ -73,6 +64,6 @@ export const SaveQueueActionBar = () => {
</AnimatePresence>
)

if (typeof document === 'undefined') return null
if (typeof document === 'undefined' || !document.body) return null
return createPortal(content, document.body)
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { THRESHOLD_COUNT } from '@supabase/pg-meta/src/query/table-row-query'
import { keepPreviousData } from '@tanstack/react-query'
import { isEqual } from 'lodash'
import { ChevronDown, List } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'

import { useParams } from 'common'
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
import type { Sort } from 'components/grid/types'
import { InlineLink } from 'components/ui/InlineLink'
import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { isEqual } from 'lodash'
import { ChevronDown, List } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
type RoleImpersonationState,
useRoleImpersonationStateSnapshot,
Expand All @@ -23,6 +22,7 @@ import {
Popover_Shadcn_,
} from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'

import { DropdownControl } from '../../common/DropdownControl'
import SortRow from './SortRow'

Expand Down Expand Up @@ -278,8 +278,10 @@ export const SortPopoverPrimitive = ({
const hasSortNotPK = localSorts.some(
(x) => !snap.table.columns.find((y) => x.column === y.name)?.isPrimaryKey
)
if (hasSortNotPK) setShowWarning(true)
} else onSelectApplySorts()
if (hasSortNotPK) return setShowWarning(true)
}

onSelectApplySorts()
}}
>
Apply sorting
Expand Down
14 changes: 6 additions & 8 deletions apps/studio/components/grid/hooks/useOperationQueueActions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { useQueryClient } from '@tanstack/react-query'
import { useCallback } from 'react'
import { toast } from 'sonner'

import { tableRowKeys } from 'data/table-rows/keys'
import { useOperationQueueSaveMutation } from 'data/table-rows/operation-queue-save-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { useCallback } from 'react'
import { toast } from 'sonner'
import { useGetImpersonatedRoleState } from 'state/role-impersonation-state'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { QueuedOperation } from 'state/table-editor-operation-queue.types'
Expand All @@ -30,6 +29,7 @@ export function useOperationQueueActions(options: UseOperationQueueActionsOption
useOperationQueueSaveMutation({
onSuccess: () => {
snap.clearQueue()
snap.closeSidePanel()
toast.success('Changes saved successfully')
onSaveSuccess?.()
},
Expand All @@ -40,12 +40,10 @@ export function useOperationQueueActions(options: UseOperationQueueActionsOption
})

const isSaving = snap.operationQueue.status === 'saving' || isMutationPending
const operations = snap.operationQueue.operations as readonly QueuedOperation[]

const handleSave = useCallback(() => {
if (!project) return

const operations = snap.operationQueue.operations as readonly QueuedOperation[]
if (operations.length === 0) return
if (!project || operations.length === 0) return

snap.setQueueStatus('saving')

Expand All @@ -55,7 +53,7 @@ export function useOperationQueueActions(options: UseOperationQueueActionsOption
operations,
roleImpersonationState: getImpersonatedRoleState(),
})
}, [snap, project, saveOperationQueue, getImpersonatedRoleState])
}, [snap, project, operations, saveOperationQueue, getImpersonatedRoleState])

const handleCancel = useCallback(() => {
// Get unique table IDs from the queue before clearing
Expand Down
Loading
Loading