From 73a2d7d91351c5e485624470d9a0fb1b16e0e7fb Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 16 Sep 2025 14:57:42 +1000 Subject: [PATCH 1/6] introduce ui rules (#38728) --- .cursor/rules/studio-ui.mdc | 253 ++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 .cursor/rules/studio-ui.mdc diff --git a/.cursor/rules/studio-ui.mdc b/.cursor/rules/studio-ui.mdc new file mode 100644 index 0000000000000..67db62a0ba1f6 --- /dev/null +++ b/.cursor/rules/studio-ui.mdc @@ -0,0 +1,253 @@ +--- +description: How to generate pages and interfaces in Studio, a web interface for managing Supabase projects +globs: +alwaysApply: true +--- + +## Project Structure + +- Next.js app using pages router +- Pages go in @apps/studio/pages + - Project related pages go in @apps/studio/pages/projects/[ref] + - Organization related pages go in @apps/studio/pages/org/[slug] +- Studio specific components go in @apps/studio/components + - Studio specific generic UI components go in @apps/studio/components/ui + - Studio specific components related to individual pages go in @apps/studio/components/interfaces e.g. @apps/studio/components/interfaces/Auth +- Generic helper functions go in @apps/studio/lib +- Generic hooks go in @apps/studio/hooks + +## Component system + +Our primitive component system is in @packages/ui and is based off shadcn/ui components. These components can be shared across all @apps e.g. studio and docs. Do not introduce new ui components unless asked to. + +- UI components are imported from this package across apps e.g. import { Button, Badge } from 'ui' +- Some components have a _Shadcn_ namespace appended to component name e.g. import { Input*Shadcn* } from 'ui' +- We should be using _Shadcn_ components where possible +- Before composing interfaces, read @packages/ui/index.tsx file for a full list of available components + +## Styling + +We use Tailwind for styling. + +- You should never use tailwind classes for colours and instead use classes we've defined ourselves + - Backgrounds // most of the time you will not need to define a background + - 'bg' used for main app surface background + - 'bg-muted' for elevating content // you can use Card instead + - 'bg-warning' for highlighting information that needs to be acted on + - 'bg-destructive' for highlighting issues + - Text + - 'text-foreground' for primary text like headings + - 'text-foreground-light' for body text + - 'text-foreground-lighter' for subtle text + - 'text-warning' for calling out information that needs action + - 'text-destructive' for calling out when something went wrong +- When needing to apply typography styles, read @apps/studio/styles/typography.scss and use one of the available classes instead of hard coding classes e.g. use "heading-default" instead of "text-sm font-medium" + +## Page structure + +When creating a new page follow these steps: + +- Create the page in @apps/studio/pages +- Use the PageLayout component that has the following props + + ```jsx + export interface NavigationItem { + id?: string + label: string + href?: string + icon?: ReactNode + onClick?: () => void + badge?: string + active?: boolean + } + + interface PageLayoutProps { + children?: ReactNode + title?: string | ReactNode + subtitle?: string | ReactNode + icon?: ReactNode + breadcrumbs?: Array<{ + label?: string + href?: string + element?: ReactNode + }> + primaryActions?: ReactNode + secondaryActions?: ReactNode + navigationItems?: NavigationItem[] + className?: string + size?: 'default' | 'full' | 'large' | 'small' + isCompact?: boolean + } + ``` + +- If a page has page related actions, add them to primary and secondary action props e.g. Users page has "Create new user" action +- If a page is within an existing section (e.g. Auth), you should use the related layout component e.g. AuthLayout +- Create a new component in @apps/studio/components/interfaces for the contents of the page +- Use ScaffoldContainer if the page should be center aligned in a container +- Use ScaffoldSection, ScaffoldSectionTitle, ScaffoldSectionDescription if the page has multiple sections + +### Page example + +```jsx +import { MyPageComponent } from 'components/interfaces/MyPage/MyPageComponent' +import AuthLayout from './AuthLayout' +import DefaultLayout from 'components/layouts/DefaultLayout' +import { ScaffoldContainer } from 'components/layouts/Scaffold' +import type { NextPageWithLayout } from 'types' + +const MyPage: NextPageWithLayout = () => { + return ( + + + + ) +} + +MyPage.getLayout = (page) => ( + + {page} + +) + +export default MyPage + +export const MyPageComponent = () => ( + +
+ My page section + A brief description of the purpose of the page +
+ // Content goes here +
+) +``` + +## Forms + +- Build forms with `react-hook-form` + `zod`. +- Use our `_Shadcn_` form primitives from `ui` and prefer `FormItemLayout` with layout="flex-row-reverse" for most controls (see `apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx`). +- Keep imports from `ui` with `_Shadcn_` suffixes. +- Forms should generally be wrapped in a Card unless specified + +### Example (single field) + +```tsx +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import * as z from 'zod' + +import { Button, Form_Shadcn_, FormField_Shadcn_, FormControl_Shadcn_, Input_Shadcn_ } from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' + +const profileSchema = z.object({ + username: z.string().min(2, 'Username must be at least 2 characters'), +}) + +export function ProfileForm() { + const form = useForm>({ + resolver: zodResolver(profileSchema), + defaultValues: { username: '' }, + mode: 'onSubmit', + reValidateMode: 'onBlur', + }) + + function onSubmit(values: z.infer) { + // handle values + } + + return ( + +
+ + + ( + + + + + + )} + /> + + + + + +
+
+ ) +} +``` + +## Cards + +- Use cards when needing to group related pieces of information +- Cards can have sections with CardContent +- Use CardFooter for actions +- Only use CardHeader and CardTitle if the card content has not been described by the surrounding content e.g. Page title or ScaffoldSectionTitle +- Use CardHeader and CardTitle when you are using multiple Cards to group related pieces of content e.g. Primary branch, Persistent branches, Preview branches + +## Sheets + +- Use a sheet when needing to reveal more complicated forms or information relating to an object and context switching away to a new page would be disruptive e.g. we list auth providers, clicking an auth provider opens a sheet with information about that provider and a form to enable, user can close sheet to go back to providers list + +## Tables + +- Use the generic ui table components for most tables +- Tables are generally contained witin a card +- If a table has associated actions, they should go above on right hand side +- If a table has associated search or filters, they should go above on left hand side +- If a table is the main content of a page, and it does not have search or filters, you can add table actions to primary and secondary actions of PageLayout +- If a table is the main content of a page section, and it does not have search or filters, you can add table actions to the right of ScaffoldSectionTitle +- For simple lists of objects you can use ResourceList with ResourceListItem instead + +### Table example + +```jsx +import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from 'ui' + +; + A list of your recent invoices. + + + Invoice + Status + Method + Amount + + + + + INV001 + Paid + Credit Card + $250.00 + + +
+``` + +## Alerts + +- Use Admonition component to alert users of important actions or restrictions in place +- Place the Admonition either at the top of the contents of the page (below page title) or at the top of the related ScaffoldSection , below ScaffoldTitle +- Use sparingly + +### Alert example + +```jsx + +``` From 274ecd8ba36c45ef4cb383070a08c2a601cf60b4 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Tue, 16 Sep 2025 15:35:59 +1000 Subject: [PATCH 2/6] New case study and blog (#38732) * Added a new case study and blog post. * ci: Autofix updates from GitHub workflow * fix yaml * fix logos * fix images temporarily * latest imagery * fix: date --------- Co-authored-by: Prashant Sridharan Co-authored-by: CoolAssPuppy <914007+CoolAssPuppy@users.noreply.github.com> --- ...cessing-large-jobs-with-edge-functions.mdx | 402 ++++++++++++++++++ apps/www/_customers/juniver.mdx | 110 +++++ apps/www/data/CustomerStories.ts | 12 + apps/www/public/customers-rss.xml | 7 + .../og.png | Bin 0 -> 38525 bytes .../three-layer-pattern-diagram.png | Bin 0 -> 74118 bytes .../blog/avatars/nick-farrant-juniver.jpg | Bin 0 -> 89143 bytes .../public/images/customers/logos/juniver.png | Bin 0 -> 9481 bytes .../images/customers/logos/light/juniver.png | Bin 0 -> 7042 bytes apps/www/public/sitemap_www.xml | 6 + 10 files changed, 537 insertions(+) create mode 100644 apps/www/_blog/2025-09-12-processing-large-jobs-with-edge-functions.mdx create mode 100644 apps/www/_customers/juniver.mdx create mode 100644 apps/www/public/images/blog/2025-09-processing-large-jobs-with-edge-functions/og.png create mode 100644 apps/www/public/images/blog/2025-09-processing-large-jobs-with-edge-functions/three-layer-pattern-diagram.png create mode 100644 apps/www/public/images/blog/avatars/nick-farrant-juniver.jpg create mode 100644 apps/www/public/images/customers/logos/juniver.png create mode 100644 apps/www/public/images/customers/logos/light/juniver.png diff --git a/apps/www/_blog/2025-09-12-processing-large-jobs-with-edge-functions.mdx b/apps/www/_blog/2025-09-12-processing-large-jobs-with-edge-functions.mdx new file mode 100644 index 0000000000000..35dc7d0132a45 --- /dev/null +++ b/apps/www/_blog/2025-09-12-processing-large-jobs-with-edge-functions.mdx @@ -0,0 +1,402 @@ +--- +title: Processing large jobs with Edge Functions, Cron, and Queues +description: Learn how to build scalable data processing pipelines using Supabase Edge Functions, cron jobs, and database queues and handle large workloads without timeouts or crashes. +author: prashant +image: 2025-09-processing-large-jobs-with-edge-functions/og.png +thumb: 2025-09-processing-large-jobs-with-edge-functions/og.png +categories: + - postgres + - edge functions + - cron + - queues +date: 2025-09-16:08:00 +toc_depth: 2 +--- + +When you're building applications that process large amounts of data, you quickly run into a fundamental problem: trying to do everything at once leads to timeouts, crashes, and frustrated users. The solution isn't to buy bigger servers. It's to break big jobs into small, manageable pieces. + +Supabase gives you three tools that work beautifully together for this: [Edge Functions](/edge-functions) for serverless compute, [Cron](/modules/cron) for scheduling, and database [queues](/modules/queues) for reliable job processing. + +Here's how to use them to build a system that can handle serious scale. + +## The three-layer pattern + +The architecture is simple but powerful. Think of it like an assembly line: + +**Collection**: Cron jobs run Edge Functions that discover work and add tasks to queues + +**Distribution**: Other cron jobs route tasks from main queues to specialized processing queues + +**Processing**: Specialized workers handle specific types of tasks from their assigned queues + +A diagram showing the three-layer pattern described above + +This breaks apart the complexity. Instead of one giant function that scrapes websites, processes content with AI, and stores everything, you have focused functions that each do one thing well. + +## Real example: Building an NFL news aggregator + +Let's say you want to build a dashboard that tracks NFL (American football) news from multiple sources including NFL-related websites and NFL-related videos on YouTube, automatically tags articles by topic, and lets users search by player or team. When they see an article they’re interested in, they can click on it and visit the website that hosts the article. It’s like a dedicated Twitter feed for the NFL without any of the toxicity. + +This sounds straightforward, but at scale this becomes complex fast. You need to monitor dozens of news sites, process hundreds of articles daily, make API calls to OpenAI for content analysis, generate vector embeddings for search, and store everything efficiently. Do this wrong and a single broken webpage crashes your entire pipeline. + +We need to build a more resilient approach. With Supabase Edge Functions, Cron, and Queues, we have the building blocks for a robust content extraction and categorization pipeline. + +## Setting up the foundation + +Everything starts with the [database](/database), and Supabase is Postgres at its core. We know what we’re getting: scalable, dependable, and standard. + +The database design for the application follows a clean pattern. You have content tables for storing articles and videos, queue tables for managing work, entity tables for NFL players and teams, and relationship tables linking everything together. For example: + +```sql +create table articles ( + url text unique not null, + headline text, + content text, + embedding vector(1536) +); +``` + +## Collection: Finding new content + +The collection layer seeks out new NFL-related content and runs on a schedule to discover new articles and videos. We create a collector for every site we want to search. A cron job triggers every 30 minutes to begin collection: + +```sql +SELECT cron.schedule( + 'nfl-collector', + '*/30 * * * *', + $$SELECT net.http_post(url := '[https://your-project.supabase.co/functions/v1/collect-content')$$](https://your-project.supabase.co/functions/v1/collect-content')$$) +); +``` + +The Edge Function does the actual scraping. The trick is being selective about what you collect: + +```tsx +function isRelevantArticle(url: string): boolean { + return url.includes('/news/') && !url.includes('/video/') +} +``` + +This simple filter prevents collecting promotional content or videos. You only want actual news articles. + +When parsing HTML, you need to handle relative URLs properly: + +```tsx +if (href.startsWith('/')) { + href = BASE_URL + href +} +``` + +And always deduplicate within a single scraping session: + +```tsx +const seen = new Set() +if (!seen.has(href)) { + seen.add(href) + articles.push({ url: href, site: 'nfl' }) +} +``` + +For database insertion, let the database handle duplicates rather than checking in your application: + +```tsx +const { error } = await supabase.from('articles').insert({ url, site }) +if (error && !error.message.includes('duplicate')) { + console.error(`Error inserting: ${url}`, error) +} +``` + +This approach is more reliable than complex application-level deduplication logic. + +## Distribution: Smart routing + +The distribution layer identifies articles that need processing and routes them to appropriate queues. The key insight is using separate queue tables for different content sources. [NFL.com](http://NFL.com) articles need different parsing than ESPN articles, so they get routed to specialized processors. It runs more frequently than collection: every 5 minutes: + +```sql +SELECT cron.schedule( + 'distributor', + '*/5 * * * *', + $$SELECT net.http_post(url := '[https://your-project.supabase.co/functions/v1/distribute-work')$$](https://your-project.supabase.co/functions/v1/distribute-work')$$) +); +``` + +The Edge Function finds unprocessed articles using a simple SQL query: + +```tsx +const { data } = await supabase + .from('articles') + .select('url, site') + .is('headline', null) // Missing headline means unprocessed + .limit(50) +``` + +Then it routes based on the source site: + +```tsx +if ([article.site](http://article.site) === "nfl") { + await supabase.from("nfl_queue").insert({ url: article.url }); +} else if ([article.site](http://article.site) === "espn") { + await supabase.from("espn_queue").insert({ url: article.url }); +} +``` + +This separation is crucial because each site has different HTML structures and parsing requirements. + +## Processing: The heavy lifting + +Each content source gets its own processor that runs on its own schedule. [NFL.com](http://NFL.com) gets processed every 15 seconds because it's high-priority: + +```sql +SELECT cron.schedule( + 'nfl-processor', + '*/15 * * * *', + $$SELECT net.http_post(url := '[https://your-project.supabase.co/functions/v1/process-nfl')$$](https://your-project.supabase.co/functions/v1/process-nfl')$$) +); +``` + +The processor handles one article at a time to stay within Edge Function timeout limits: + +```tsx +const { data } = await supabase + .from('nfl_queue') + .select('id, url') + .eq('processed', false) + .order('created_at') + .limit(1) +``` + +Content extraction requires site-specific CSS selectors: + +```tsx +const headline = $('h1').first().text().trim() +const content = $('.article-body').text().trim() || $('article').text().trim() +``` + +Date parsing often needs custom logic for each site's format: + +```tsx +const dateText = $('.publish-date').text() +const match = dateText.match(/(\w+ \d+, \d{4})/) +if (match) { + publication_date = new Date(match[1]) +} +``` + +After scraping, the article gets analyzed with AI to extract entities: + +```tsx +const result = await classifyArticle(headline, content) +const playerIds = await upsertPlayers(supabase, result.players) +const teamIds = await upsertTeams(supabase, result.teams) +``` + +Finally, create the relationships and generate embeddings: + +```tsx +await supabase.from("article_players").insert( + [playerIds.map](http://playerIds.map)(id => ({ article_url: url, player_id: id })) +); + +const embedding = await generateEmbedding(`${headline}\n${content}`); +await supabase.from("articles").update({ embedding }).eq("url", url); +``` + +The critical pattern is the finally block. We use it to always mark queue items as processed, preventing infinite loops when articles fail to process: + +```tsx +try { + // Process article +} finally { + await supabase.from("nfl_queue") + .update({ processed: true }) + .eq("id", [item.id](http://item.id)); +} +``` + +### Monitoring with Sentry + +While the finally block prevents infinite loops, you still need visibility into what's actually failing. Sentry integration gives you detailed error tracking for your Edge Functions. + +First, set up Sentry in your Edge Function: + +```jsx +import { captureException, init } from 'https://deno.land/x/sentry/index.js' + +init({ + dsn: Deno.env.get('SENTRY_DSN'), + environment: Deno.env.get('ENVIRONMENT') || 'production', +}) +``` + +Then wrap your processing logic with proper error capture: + +```jsx +try { + const content = await scrapeArticle(url); + const analysis = await classifyArticle(headline, content); + await storeArticle(article, analysis); +} catch (error) { + *// Capture the full context for debugging* + captureException(error, { + tags: { + function: "nfl-processor", + site: article.site + }, + extra: { + url: article.url, + queueId: queueItem.id + } + }); + console.error(`Failed to process ${url}:`, error); +} finally { + await supabase.from("nfl_queue") + .update({ processed: true }) + .eq("id", queueItem.id); +} +``` + +This gives you real-time alerts when processors fail and detailed context for debugging production issues. + +## Processing user interactions through the pipeline + +The same pipeline pattern works for user-generated events. When someone clicks, shares, or saves an article, you don't want to block their response while updating trending scores for every player and team mentioned in that article. + +Instead, treat interactions like any other job to be processed: + +```tsx +// Just record the interaction quickly +await supabase.from('interaction_queue').insert({ + article_url: url, + user_id: userId, + interaction_type: 'share', +}) +``` + +Then let a separate cron job process the trending updates in batches: + +```sql +SELECT cron.schedule( + 'process-interactions', + '*/2 * * * *', -- Every 2 minutes + $$SELECT net.http_post(url := '[https://your-project.supabase.co/functions/v1/process-interactions')$$](https://your-project.supabase.co/functions/v1/process-interactions')$$) +); +``` + +The processor can handle multiple interactions efficiently: + +```tsx +const { data: interactions } = await supabase + .from('interaction_queue') + .select('*') + .eq('processed', false) + .limit(100) +``` + +This keeps your user interface snappy while ensuring trending scores get updated reliably. If the trending processor goes down, interactions are safely queued and will be processed when it recovers. + +## AI-powered content scoring + +To surface the most important content automatically, use AI to analyze article context and assign importance scores. + +Define scores for different news types: + +```tsx +const CONTEXT_SCORES = { + championship: 9, + trade: 6, + injury: 4, + practice: 1, +} +``` + +Prompt OpenAI with structured output: + +```tsx +const prompt = `Analyze this headline: "${headline}" +Return JSON: {"context": "trade|injury|etc", "score": 1-9}`; + +const result = await [openai.chat](http://openai.chat).completions.create({ + model: "gpt-3.5-turbo", + response_format: { type: "json_object" } +}); +``` + +Process articles in batches to manage API costs: + +```tsx +const unprocessed = articles.filter((a) => !processedUrls.has(a.url)).slice(0, 10) + +for (const article of unprocessed) { + const analysis = await analyzeArticle(article) + await storeAnalysis(article.url, analysis) +} +``` + +## Background tasks for expensive operations + +Some operations are too expensive to run synchronously, even in your cron-triggered processors. Vector embedding generation and bulk AI analysis benefit from background task patterns. + +Edge Functions support background tasks that continue processing after the main response completes: + +typescript + +```jsx +*// In your article processor* +const article = await scrapeAndStore(url); + +*// Start expensive operations in background* +const backgroundTasks = [ + generateEmbedding(article), + analyzeWithAI(article), + updateRelatedContent(article) +]; + +*// Run background tasks without blocking the main flow* +Promise.all(backgroundTasks).catch(error => { + captureException(error, { + tags: { operation: "background-tasks" }, + extra: { articleUrl: url } + }); +}); + +*// Main processing continues immediately* +await markAsProcessed(queueItem.id); +``` + +For operations that might take longer than Edge Function limits, break them into smaller background chunks: + +typescript + +```jsx +async function generateEmbeddingInBackground(article: Article) { + *// Process content in chunks* + const chunks = splitIntoChunks(article.content, 1000); + + for (const chunk of chunks) { + await new Promise(resolve => { + *// Use background task for each chunk* + setTimeout(async () => { + const embedding = await generateEmbedding(chunk); + await storeEmbedding(article.id, embedding); + resolve(void 0); + }, 0); + }); + } +} +``` + +This pattern keeps your main processing pipeline fast while ensuring expensive operations complete reliably. + +## Why this works + +This pattern succeeds because it embraces the constraints of serverless computing rather than fighting them. Edge Functions have time limits, so you process one item at a time. External APIs have rate limits, so you control timing with cron schedules. Failures happen, so you isolate them to individual tasks. + +The result is a system that scales horizontally by adding more cron jobs and queues. Each component can fail independently without bringing down the whole pipeline. Users get fresh content as it becomes available rather than waiting for batch jobs to complete. + +Most importantly, it's built entirely with Supabase primitives — no external queue systems or job schedulers required. You get enterprise-grade reliability with startup simplicity. diff --git a/apps/www/_customers/juniver.mdx b/apps/www/_customers/juniver.mdx new file mode 100644 index 0000000000000..aa19fbf1f9854 --- /dev/null +++ b/apps/www/_customers/juniver.mdx @@ -0,0 +1,110 @@ +--- +name: Juniver +title: 'Juniver built a self-serve B2B platform with Supabase to scale eating disorder recovery' +# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories': +# meta_title: +description: Juniver switched from Firebase to Supabase and saw immediate improvements in developer experience and performance. +# Use meta_description to add a custom meta description. Otherwise it defaults to {description}: +meta_description: Juniver switched from Firebase to Supabase and saw immediate improvements in developer experience and performance. +author: prashant +author_title: Prashant Sridharan +author_url: https://github.com/CoolAssPuppy +author_image_url: https://avatars.githubusercontent.com/u/914007?v=4 +logo: /images/customers/logos/juniver.png +logo_inverse: /images/customers/logos/light/juniver.png +og_image: /images/customers/og/juniver.jpg +tags: + - supabase +date: '2025-01-15' +company_url: https://www.joinjuniver.com +misc: [{ label: 'Founded', text: 'United Kingdom' }] +about: Science-based recovery program for disordered eating +# "healthcare" | "fintech" | "ecommerce" | "education" | "gaming" | "media" | "real-estate" | "saas" | "social" | "analytics" | "ai" | "developer-tools" +industry: ['healthcare', 'ai', 'saas'] +# "startup" | "enterprise" | "indie_dev" +company_size: 'startup' +# "Asia" | "Europe" | "North America" | "South America" | "Africa" | "Oceania" +region: 'Europe' +# "database" | "auth" | "storage" | "realtime" | "functions" | "vector" +supabase_products: ['database', 'auth', 'functions'] +--- + + + For me, the biggest benefit of Supabase is developer experience. My expertise doesn't lie in + databases and infrastructure. It really didn't take much time at all to spin up this product with + Supabase. + + +[Juniver](https://www.joinjuniver.com/) is an iOS app that guides people through recovery from eating disorders and disordered eating. The product combines an AI recovery coach, a thoroughly researched curriculum, and self-serve tools designed for moments of need. Juniver is expanding from B2C into B2B2C, licensing its platform to clinics and clinicians who then offer it to patients. + +## The challenge + +Juniver needed to evolve its backend to support a new B2B2C product, without risking the stability of its consumer app. + +- Launch a self-serve, automated, scalable platform for clinics and clinicians +- Enforce strict privacy and access controls across clinics, clinicians, patients, and licenses +- Integrate tightly with Stripe and webhooks to automate onboarding and license provisioning +- Move quickly with a lean team, choosing tools that won't have to be replaced in a few years + +They also faced significant friction with their previous Firebase setup: + +- Document-based storage made data relationships difficult to manage +- Manual relationship handling slowed development +- Limited scalability for complex B2B requirements +- Developer productivity was constrained by Firebase's limitations + + + Time is such a big currency in startup world. With Firebase, we were really struggling with + relationships within our data. It was all done very manually, as you can expect with + document-based storage. Supabase is a relational database and it's much easier and we can move + faster. + + +## Choosing Supabase + +Juniver had long run on Google Cloud with Firebase for the mobile app. The new B2B product created a clean slate to evaluate alternatives and set a foundation for future scale. Supabase offered a relational core (Postgres) and an integrated developer experience that aligned with Juniver's needs now and later. + +**Why Supabase** + +- A relational database (Postgres) to model clean, maintainable relationships +- Integrated developer experience: visual Table Editor with inline SQL, clear logs, SDKs +- Strong security posture with Row Level Security, plus an easy way to "view as user" to validate policies +- Built-in primitives (Auth, Functions) to save time and build with best-of-breed products + + + The Supabase developer experience is so clear. I've got everything at my fingertips. I'm in the + Table Editor. The SQL editor is literally right below the table editor. The feature you have to + see the table from a different user's perspective to test your Row Level Security is a wonderful + little feature that I use all the time. + + +## The approach + +Juniver used the B2B product as a greenfield build on Supabase, avoiding risky dependencies on the legacy Firebase data while preparing for an eventual B2C migration. + +- **Getting started** + - Spun up a new Supabase project and schema for clinics, clinicians, patients, and licenses + - Leaned on documentation and the iOS SDK to accelerate the build +- **Implementation highlights** + - **Database (Postgres):** Relational schema with strict Row Level Security; frequent use of "view as user" to validate policies + - **Auth & OAuth:** Managing identities for clinics and patients with fine-grained access + - **Webhooks + Edge Functions:** Stripe purchase triggers an automated sequence to authenticate partners, provision license codes, and send emails + - **Operational visibility:** Edge Function logs and an integrated console for fast debugging and iteration + + + You have to expect, plan, and build for scale. We can scale exponentially with Supabase. + + +## The results + +Juniver's B2B platform is out in the wild and early in adoption, but the team is already seeing meaningful wins in developer experience and delivery speed. + +- Rapidly build-out of a self-serve, automated B2B platform using out-of-the-box Supabase features +- Faster iteration through an integrated UI, inline SQL, clear logs, and RLS policy testing +- A relational foundation that supports today's B2B needs and tomorrow's B2C migration +- Streamlined development process with Supabase's integrated tooling + + + My advice to developers thinking of moving to Supabase: Try it, it's free. Replicate your project. + You'll see how much easier it is to think about your data from a relational point of view. + diff --git a/apps/www/data/CustomerStories.ts b/apps/www/data/CustomerStories.ts index b12e56ac87810..063854d9d2a70 100644 --- a/apps/www/data/CustomerStories.ts +++ b/apps/www/data/CustomerStories.ts @@ -18,6 +18,18 @@ export type CustomerStoryType = { } export const data: CustomerStoryType[] = [ + { + type: 'Customer Story', + title: + 'Juniver built automated B2B workflows with Supabase Edge Functions and Row Level Security', + description: + 'Juniver switched from Firebase to Supabase and saw immediate improvements in developer experience and performance.', + organization: 'Juniver', + imgUrl: 'images/customers/logos/juniver.png', + logo: '/images/customers/logos/juniver.png', + logo_inverse: '/images/customers/logos/light/juniver.png', + url: '/customers/juniver', + }, { type: 'Customer Story', title: 'Kayhan Space saw 8x improvement in developer speed when moving to Supabase', diff --git a/apps/www/public/customers-rss.xml b/apps/www/public/customers-rss.xml index 2c776ae03103e..57ff2acef841c 100644 --- a/apps/www/public/customers-rss.xml +++ b/apps/www/public/customers-rss.xml @@ -70,6 +70,13 @@ Scaling seamlessly to 5,000+ paying customers & millions of emails sent daily with Supabase Fri, 21 Feb 2025 00:00:00 -0700 + + https://supabase.com/customers/juniver + Juniver built a self-serve B2B platform with Supabase to scale eating disorder recovery + https://supabase.com/customers/juniver + Juniver switched from Firebase to Supabase and saw immediate improvements in developer experience and performance. + Wed, 15 Jan 2025 00:00:00 -0700 + https://supabase.com/customers/e2b E2B: Accelerating AI-Driven Development with Supabase diff --git a/apps/www/public/images/blog/2025-09-processing-large-jobs-with-edge-functions/og.png b/apps/www/public/images/blog/2025-09-processing-large-jobs-with-edge-functions/og.png new file mode 100644 index 0000000000000000000000000000000000000000..cca6165b2114c8d232b49241803f5ce4ef43ec3a GIT binary patch literal 38525 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 D%HOUn literal 0 HcmV?d00001 diff --git a/apps/www/public/images/blog/2025-09-processing-large-jobs-with-edge-functions/three-layer-pattern-diagram.png b/apps/www/public/images/blog/2025-09-processing-large-jobs-with-edge-functions/three-layer-pattern-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..3046914da4298e12e27eead89ed222364d49cf07 GIT binary patch literal 74118 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%0A-sHd{a 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 PY+yAdO~p)k^9TP0=Grj* literal 0 HcmV?d00001 diff --git a/apps/www/public/images/blog/avatars/nick-farrant-juniver.jpg b/apps/www/public/images/blog/avatars/nick-farrant-juniver.jpg new file mode 100644 index 0000000000000000000000000000000000000000..49c7335de71c8e7d23669f5d141d8cfd8f837dd5 GIT binary patch literal 89143 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 W1EAgIV2B2WRd0)>+)8M`HaE`Tjkq66 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�k$s+=A@hmTq6im2Si_HhH-3CK~WNX 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 bjw$~i#^pROEaa0800000NkvXXu0mjfDj20V literal 0 HcmV?d00001 diff --git a/apps/www/public/images/customers/logos/light/juniver.png b/apps/www/public/images/customers/logos/light/juniver.png new file mode 100644 index 0000000000000000000000000000000000000000..90f22bdd80dccdb9c678a323489652c66da4a887 GIT binary patch literal 7042 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 0.5 + + + https://supabase.com/customers/juniver + weekly + 0.5 + https://supabase.com/customers/kayhanspace From 7622b06fbbb19bcc3f570a91df1ef679a00950e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 16 Sep 2025 15:29:14 +0800 Subject: [PATCH 3/6] chore: transfer docs requirements (#38730) --- apps/docs/content/guides/platform/project-transfer.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/guides/platform/project-transfer.mdx b/apps/docs/content/guides/platform/project-transfer.mdx index 14a2429e6dd04..91fe31b7542bf 100644 --- a/apps/docs/content/guides/platform/project-transfer.mdx +++ b/apps/docs/content/guides/platform/project-transfer.mdx @@ -33,7 +33,10 @@ Target organization - the organization you want to move the project to - You need to be the owner of the source organization. - You need to be at least a member of the target organization you want to move the project to. -- Projects with support tier add-ons cannot be transferred at this point. [Open a support ticket](/dashboard/support/new?category=billing&subject=Transfer%20project). +- No active GitHub integration connection +- No project-scoped roles pointing to the project (Team/Enterprise plan) +- No log drains configured +- Target organization is not managed by Vercel Marketplace (currently unsupported) ## Usage-billing and project add-ons From 48625868d3c6c37bb2f4bfe23e5f617410f6119e Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Tue, 16 Sep 2025 10:53:39 +0300 Subject: [PATCH 4/6] Correction to the patent blog post (#38644) --- apps/www/_blog/2025-09-09-orioledb-patent-free.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/_blog/2025-09-09-orioledb-patent-free.mdx b/apps/www/_blog/2025-09-09-orioledb-patent-free.mdx index 08404b9f9164a..f2e699e412068 100644 --- a/apps/www/_blog/2025-09-09-orioledb-patent-free.mdx +++ b/apps/www/_blog/2025-09-09-orioledb-patent-free.mdx @@ -42,7 +42,7 @@ OrioleDB will continue as an [open source project](https://github.com/orioledb/o ## License compatibility with Postgres -[OrioleDB License](https://github.com/orioledb/orioledb?tab=License-1-ov-file#readme) is based on the [PostgreSQL License](https://github.com/postgres/postgres?tab=License-1-ov-file). To reinforce the IP compatibility, Supabase is making available a non-exclusive license of U.S. Patent (“Durable multiversion B+-tree”) to all OrioleDB users, including proprietary forks, in accordance with the OrioleDB license. The patent is intended as a shield, not a sword, to protect Open Source from hostile IP claims. +[OrioleDB License](https://github.com/orioledb/orioledb?tab=Apache-2.0-1-ov-file#readme) is now Apache 2.0. This license is quite similar to [PostgreSQL License](https://github.com/postgres/postgres?tab=License-1-ov-file), but explicitly grants patent usage. With this change, Supabase is making available a non-exclusive license of U.S. Patent (“Durable multiversion B+-tree”) to all OrioleDB users, including proprietary forks. The patent is intended as a shield, not a sword, to protect Open Source from hostile IP claims. ## **Aligned with Postgres** From d46525eac14feb911e87d2b275239fe5782249ef Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 16 Sep 2025 17:05:57 +0800 Subject: [PATCH 5/6] Chore/swap use check permissions with use async check project permissions part 8 (Season Finale) (#38619) * Update perms checking in audit logs * Deprecate useCheckPermissions, useIsPermissionsLoaded and useCheckProjectPermissions as they're no longer used * Rename useAsyncCheckProjectPermissions to useAsyncCheckPermissions * Fix TS --- .../grid/components/header/Header.tsx | 4 +- .../interfaces/APIKeys/APIKeyDeleteDialog.tsx | 4 +- .../interfaces/APIKeys/ApiKeyPill.tsx | 8 +- .../interfaces/APIKeys/PublishableAPIKeys.tsx | 8 +- .../interfaces/APIKeys/SecretAPIKeys.tsx | 4 +- .../APIKeys/hooks/useApiKeysVisibility.ts | 4 +- .../Auth/AdvancedAuthSettingsForm.tsx | 6 +- .../interfaces/Auth/AuditLogsForm.tsx | 14 ++-- .../Auth/AuthProvidersForm/ProviderForm.tsx | 4 +- .../BasicAuthSettingsForm.tsx | 6 +- .../Auth/EmailTemplates/TemplateEditor.tsx | 4 +- .../interfaces/Auth/Hooks/AddHookDropdown.tsx | 7 +- .../interfaces/Auth/Hooks/HookCard.tsx | 7 +- .../MfaAuthSettingsForm.tsx | 6 +- .../PolicyEditorPanel/PolicyDetailsV2.tsx | 4 +- .../Auth/Policies/PolicyEditorPanel/index.tsx | 4 +- .../Policies/PolicyTableRow/PolicyRow.tsx | 4 +- .../PolicyTableRow/PolicyTableRowHeader.tsx | 6 +- .../ProtectionAuthSettingsForm.tsx | 6 +- .../interfaces/Auth/RateLimits/RateLimits.tsx | 6 +- .../Auth/RedirectUrls/RedirectUrlList.tsx | 4 +- .../SessionsAuthSettingsForm.tsx | 6 +- .../interfaces/Auth/SiteUrl/SiteUrl.tsx | 4 +- .../interfaces/Auth/SmtpForm/SmtpForm.tsx | 6 +- .../Auth/ThirdPartyAuthForm/index.tsx | 4 +- .../interfaces/Auth/Users/AddUserDropdown.tsx | 6 +- .../interfaces/Auth/Users/CreateUserModal.tsx | 4 +- .../interfaces/Auth/Users/InviteUserModal.tsx | 4 +- .../interfaces/Auth/Users/UserOverview.tsx | 17 ++--- .../PaymentMethods/CurrentPaymentMethod.tsx | 4 +- .../Payment/PaymentMethods/PaymentMethods.tsx | 10 ++- .../BranchManagement/CreateBranchModal.tsx | 4 +- .../interfaces/BranchManagement/Overview.tsx | 8 +- .../components/interfaces/Connect/Connect.tsx | 4 +- .../Database/Backups/BackupItem.tsx | 4 +- .../Database/Backups/PITR/PITRNotice.tsx | 4 +- .../Database/Backups/PITR/PITRStatus.tsx | 4 +- .../Database/Extensions/ExtensionRow.tsx | 4 +- .../Database/Extensions/Extensions.tsx | 8 +- .../Functions/FunctionsList/FunctionList.tsx | 6 +- .../Functions/FunctionsList/FunctionsList.tsx | 11 ++- .../Database/Hooks/HooksList/HookList.tsx | 4 +- .../Database/Hooks/HooksList/HooksList.tsx | 8 +- .../Publications/PublicationsList.tsx | 8 +- .../Publications/PublicationsTableItem.tsx | 4 +- .../Publications/PublicationsTables.tsx | 8 +- .../interfaces/Database/Roles/RolesList.tsx | 4 +- .../interfaces/Database/Tables/ColumnList.tsx | 4 +- .../interfaces/Database/Tables/TableList.tsx | 16 ++-- .../Triggers/TriggersList/TriggerList.tsx | 9 +-- .../Triggers/TriggersList/TriggersList.tsx | 11 ++- .../DiskManagement/DiskManagementForm.tsx | 4 +- .../DiskManagementReviewAndSubmitDialog.tsx | 4 +- .../interfaces/Docs/Description.tsx | 4 +- .../EdgeFunctionDetails.tsx | 4 +- .../EdgeFunctionSecret.tsx | 7 +- .../EdgeFunctionSecrets.tsx | 9 +-- .../interfaces/GraphQL/GraphiQL.tsx | 4 +- .../Home/NewProjectPanel/APIKeys.tsx | 4 +- .../HomeNew/CustomReportSection.tsx | 8 +- .../CronJobs/CreateCronJobSheet.tsx | 4 +- .../Integrations/Queues/QueuesSettings.tsx | 4 +- .../Integrations/Vault/Secrets/SecretRow.tsx | 4 +- .../Vault/Secrets/SecretsManagement.tsx | 14 ++-- .../Integrations/Webhooks/ListTab.tsx | 4 +- .../Integrations/Webhooks/OverviewTab.tsx | 4 +- .../Integrations/Wrappers/OverviewTab.tsx | 4 +- .../Integrations/Wrappers/WrapperRow.tsx | 4 +- .../Integrations/Wrappers/WrappersTab.tsx | 4 +- .../interfaces/JwtSecrets/jwt-settings.tsx | 8 +- .../Organization/AuditLogs/AuditLogs.tsx | 8 +- .../BillingBreakdown/BillingBreakdown.tsx | 12 +-- .../BillingCustomerData.tsx | 6 +- .../BillingSettings/BillingEmail.tsx | 10 ++- .../CostControl/CostControl.tsx | 8 +- .../CostControl/SpendCapSidePanel.tsx | 4 +- .../BillingSettings/CreditBalance.tsx | 8 +- .../BillingSettings/CreditTopUp.tsx | 4 +- .../Subscription/PlanUpdateSidePanel.tsx | 4 +- .../Subscription/Subscription.tsx | 8 +- .../Organization/Documents/SOC2.tsx | 8 +- .../Documents/SecurityQuestionnaire.tsx | 8 +- .../GeneralSettings/DataPrivacyForm.tsx | 4 +- .../DeleteOrganizationButton.tsx | 4 +- .../OrganizationDetailsForm.tsx | 4 +- .../IntegrationSettings.tsx | 8 +- .../InvoicesSettings/InvoicesSection.tsx | 4 +- .../Organization/OAuthApps/OAuthAppRow.tsx | 6 +- .../Organization/OAuthApps/OAuthApps.tsx | 10 ++- .../OAuthApps/OAuthSecrets/OAuthSecrets.tsx | 7 +- .../OAuthApps/OAuthSecrets/SecretRow.tsx | 7 +- .../SecuritySettings/SecuritySettings.tsx | 10 ++- .../TeamSettings/MemberActions.tsx | 6 +- .../interfaces/Organization/Usage/Usage.tsx | 8 +- .../interfaces/Realtime/Inspector/Header.tsx | 4 +- .../Realtime/Inspector/MessagesTable.tsx | 4 +- .../interfaces/Realtime/RealtimeSettings.tsx | 4 +- .../components/interfaces/Reports/Reports.tsx | 6 +- .../SQLEditor/SQLTemplates/SQLQuickstarts.tsx | 4 +- .../SQLEditor/SQLTemplates/SQLTemplates.tsx | 4 +- .../components/interfaces/SQLEditor/hooks.ts | 4 +- .../Settings/API/PostgrestConfig.tsx | 4 +- .../Settings/Addons/CustomDomainSidePanel.tsx | 4 +- .../Settings/Addons/IPv4SidePanel.tsx | 4 +- .../Settings/Addons/PITRSidePanel.tsx | 4 +- .../Settings/Database/BannedIPs.tsx | 16 ++-- .../ConnectionPooling/ConnectionPooling.tsx | 4 +- .../DatabaseSettings/ResetDbPassword.tsx | 4 +- .../Database/DiskSizeConfiguration.tsx | 4 +- .../NetworkRestrictions.tsx | 4 +- .../Settings/Database/SSLConfiguration.tsx | 4 +- .../ProjectComplianceMode.tsx | 4 +- .../CustomDomainsConfigureHostname.tsx | 4 +- .../DeleteProjectButton.tsx | 12 +-- .../interfaces/Settings/General/General.tsx | 16 ++-- .../Infrastructure/PauseProjectButton.tsx | 4 +- .../Infrastructure/RestartServerButton.tsx | 4 +- .../TransferProjectButton.tsx | 4 +- .../InstanceConfiguration.tsx | 7 +- .../InstanceNode.tsx | 7 +- .../InfrastructureConfiguration/MapView.tsx | 7 +- .../GitHubIntegrationConnectionForm.tsx | 6 +- .../GithubIntegration/GithubSection.tsx | 4 +- .../VercelIntegration/VercelSection.tsx | 8 +- .../interfaces/Settings/Logs/LogTable.tsx | 4 +- .../interfaces/Storage/BucketRow.tsx | 7 +- .../interfaces/Storage/CreateBucketModal.tsx | 7 +- .../StorageExplorer/ColumnContextMenu.tsx | 7 +- .../StorageExplorer/FileExplorerColumn.tsx | 7 +- .../StorageExplorer/FileExplorerHeader.tsx | 7 +- .../FileExplorerHeaderSelection.tsx | 7 +- .../StorageExplorer/FileExplorerRow.tsx | 7 +- .../StorageExplorer/FolderContextMenu.tsx | 7 +- .../StorageExplorer/ItemContextMenu.tsx | 7 +- .../Storage/StorageExplorer/PreviewPane.tsx | 7 +- .../StorageSettings/CreateCredentialModal.tsx | 4 +- .../Storage/StorageSettings/S3Connection.tsx | 10 ++- .../StorageSettings/StorageCredItem.tsx | 4 +- .../StorageSettings/StorageSettings.tsx | 10 ++- .../__tests__/CreateBucketModal.test.tsx | 2 +- .../TableGridEditor/GridHeaderActions.tsx | 10 ++- .../TableGridEditor/TableGridEditor.tsx | 6 +- .../BranchingPITRNotice.tsx | 4 +- .../EdgeFunctionDetailsLayout.tsx | 4 +- .../layouts/LogsLayout/LogsLayout.tsx | 4 +- .../ProjectLayout/PauseFailedState.tsx | 12 +-- .../PausedState/ProjectPausedState.tsx | 4 +- .../ProjectLayout/RestoreFailedState.tsx | 10 +-- .../layouts/ReportsLayout/ReportMenuItem.tsx | 4 +- .../layouts/ReportsLayout/ReportsMenu.tsx | 4 +- .../layouts/SQLEditorLayout/SQLEditorMenu.tsx | 4 +- .../SQLEditorNavV2/SQLEditorTreeViewItem.tsx | 4 +- .../SQLEditorLayout/SqlEditor.Commands.tsx | 4 +- .../TableEditorLayout/TableEditorLayout.tsx | 4 +- .../TableEditorLayout/TableEditorMenu.tsx | 4 +- .../studio/components/layouts/Tabs/NewTab.tsx | 4 +- .../ui/AIAssistantPanel/AIOptInModal.tsx | 4 +- .../AIAssistantPanel/DisplayBlockRenderer.tsx | 4 +- .../ui/AIAssistantPanel/MessageMarkdown.tsx | 4 +- .../ui/ProjectSettings/DisplayApiSettings.tsx | 4 +- .../ProjectSettings/ToggleLegacyApiKeys.tsx | 8 +- apps/studio/components/ui/SchemaSelector.tsx | 4 +- apps/studio/components/ui/UpgradeToPro.tsx | 4 +- .../data/config/project-settings-v2-query.ts | 4 +- .../organization-customer-profile-query.ts | 4 +- .../organization-payment-methods-query.ts | 4 +- .../organization-tax-id-query.ts | 4 +- .../iceberg-wrapper-create-mutation.ts | 4 +- .../data/subscriptions/org-plans-query.ts | 4 +- .../subscriptions/org-subscription-query.ts | 4 +- apps/studio/hooks/forms/useAIOptInForm.ts | 4 +- apps/studio/hooks/misc/useCheckPermissions.ts | 75 +------------------ apps/studio/pages/new/[slug].tsx | 4 +- .../pages/project/[ref]/auth/advanced.tsx | 8 +- .../pages/project/[ref]/auth/audit-logs.tsx | 8 +- .../studio/pages/project/[ref]/auth/hooks.tsx | 8 +- apps/studio/pages/project/[ref]/auth/mfa.tsx | 8 +- .../pages/project/[ref]/auth/policies.tsx | 4 +- .../pages/project/[ref]/auth/protection.tsx | 8 +- .../pages/project/[ref]/auth/rate-limits.tsx | 8 +- .../pages/project/[ref]/auth/sessions.tsx | 8 +- apps/studio/pages/project/[ref]/auth/smtp.tsx | 8 +- .../pages/project/[ref]/auth/templates.tsx | 8 +- .../pages/project/[ref]/auth/third-party.tsx | 8 +- .../project/[ref]/auth/url-configuration.tsx | 8 +- .../pages/project/[ref]/branches/index.tsx | 6 +- .../project/[ref]/branches/merge-requests.tsx | 4 +- .../project/[ref]/database/backups/pitr.tsx | 8 +- .../backups/restore-to-new-project.tsx | 10 ++- .../[ref]/database/backups/scheduled.tsx | 8 +- .../project/[ref]/database/extensions.tsx | 8 +- .../project/[ref]/database/functions.tsx | 4 +- .../[ref]/database/publications/[id].tsx | 8 +- .../[ref]/database/publications/index.tsx | 8 +- .../pages/project/[ref]/database/triggers.tsx | 4 +- .../[ref]/functions/[functionSlug]/code.tsx | 7 +- .../[ref]/functions/[functionSlug]/index.tsx | 6 +- .../pages/project/[ref]/logs/auth-logs.tsx | 4 +- .../pages/project/[ref]/reports/database.tsx | 4 +- .../pages/project/[ref]/reports/index.tsx | 4 +- .../[ref]/settings/jwt/signing-keys.tsx | 4 +- .../project/[ref]/settings/log-drains.tsx | 8 +- 202 files changed, 603 insertions(+), 682 deletions(-) diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx index 821a359c06d11..696524f4d2032 100644 --- a/apps/studio/components/grid/components/header/Header.tsx +++ b/apps/studio/components/grid/components/header/Header.tsx @@ -14,7 +14,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query' import { fetchAllTableRows, useTableRowsQuery } from 'data/table-rows/table-rows-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { RoleImpersonationState } from 'lib/role-impersonation' @@ -84,7 +84,7 @@ const DefaultHeader = () => { const snap = useTableEditorTableStateSnapshot() const tableEditorSnap = useTableEditorStateSnapshot() - const { can: canCreateColumns } = useAsyncCheckProjectPermissions( + const { can: canCreateColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) diff --git a/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx b/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx index 7ab3876edeb65..5dbea60499586 100644 --- a/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx +++ b/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx @@ -7,7 +7,7 @@ import { useParams } from 'common/hooks' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { useAPIKeyDeleteMutation } from 'data/api-keys/api-key-delete-mutation' import { APIKeysData } from 'data/api-keys/api-keys-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' interface APIKeyDeleteDialogProps { @@ -19,7 +19,7 @@ export const APIKeyDeleteDialog = ({ apiKey, lastSeen }: APIKeyDeleteDialogProps const { ref: projectRef } = useParams() const [isOpen, setIsOpen] = useState(false) - const { can: canDeleteAPIKeys } = useAsyncCheckProjectPermissions( + const { can: canDeleteAPIKeys } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx b/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx index 9d060f1ff8908..87334a6e5f42b 100644 --- a/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx +++ b/apps/studio/components/interfaces/APIKeys/ApiKeyPill.tsx @@ -11,7 +11,7 @@ import CopyButton from 'components/ui/CopyButton' import { useAPIKeyIdQuery } from 'data/api-keys/[id]/api-key-id-query' import { APIKeysData } from 'data/api-keys/api-keys-query' import { apiKeysKeys } from 'data/api-keys/keys' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' export function ApiKeyPill({ @@ -28,8 +28,10 @@ export function ApiKeyPill({ const isSecret = apiKey.type === 'secret' // Permission check for revealing/copying secret API keys - const { can: canManageSecretKeys, isLoading: isLoadingPermission } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'service_api_keys') + const { can: canManageSecretKeys, isLoading: isLoadingPermission } = useAsyncCheckPermissions( + PermissionAction.READ, + 'service_api_keys' + ) // This query only runs when show=true (enabled: show) // It fetches the fully revealed API key when needed diff --git a/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx index 779a9b66e7fe9..cd0cda84cca04 100644 --- a/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/PublishableAPIKeys.tsx @@ -6,16 +6,16 @@ import { useParams } from 'common' import CopyButton from 'components/ui/CopyButton' import { FormHeader } from 'components/ui/Forms/FormHeader' import { useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { cn, EyeOffIcon, Input_Shadcn_, Skeleton, - WarningIcon, Tooltip, TooltipContent, TooltipTrigger, + WarningIcon, } from 'ui' // to add in later with follow up PR @@ -36,7 +36,7 @@ export const PublishableAPIKeys = () => { [apiKeysData] ) - const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, '*' ) @@ -106,7 +106,7 @@ const ApiKeyInput = () => { [apiKeysData] ) - const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx index 2debd28019afb..94e9523d568f7 100644 --- a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx @@ -7,7 +7,7 @@ import AlertError from 'components/ui/AlertError' import { FormHeader } from 'components/ui/Forms/FormHeader' import { APIKeysData, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import useLogsQuery from 'hooks/analytics/useLogsQuery' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Card, CardContent, EyeOffIcon, Skeleton, cn } from 'ui' import { Table, @@ -58,7 +58,7 @@ export const SecretAPIKeys = () => { isError: isErrorApiKeys, } = useAPIKeysQuery({ projectRef, reveal: false }) - const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts b/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts index f2b602522ef06..7cedd8a9f1b44 100644 --- a/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts +++ b/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react' import { useParams } from 'common' import { useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' interface ApiKeysVisibilityState { hasApiKeys: boolean @@ -19,7 +19,7 @@ interface ApiKeysVisibilityState { */ export function useApiKeysVisibility(): ApiKeysVisibilityState { const { ref: projectRef } = useParams() - const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions(PermissionAction.READ, 'api_keys') + const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.READ, 'api_keys') const { data: apiKeysData, isLoading } = useAPIKeysQuery({ projectRef, diff --git a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx index 9f498c43c16fc..26bd90de84dbf 100644 --- a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx @@ -12,7 +12,7 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { @@ -43,11 +43,11 @@ const FormSchema = z.object({ export const AdvancedAuthSettingsForm = () => { const { ref: projectRef } = useParams() const { data: organization } = useSelectedOrganizationQuery() - const { can: canReadConfig } = useAsyncCheckProjectPermissions( + const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx index eb9ac33b0963f..b3e9ccded3448 100644 --- a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx @@ -8,11 +8,10 @@ import { boolean, object } from 'yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' import { InlineLink } from 'components/ui/InlineLink' -import { NoPermission } from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useTablesQuery } from 'data/tables/tables-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { AlertDescription_Shadcn_, @@ -40,8 +39,11 @@ const AUDIT_LOG_ENTRIES_TABLE = 'audit_log_entries' export const AuditLogsForm = () => { const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + + const { can: canUpdateConfig } = useAsyncCheckPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const { data: tables = [] } = useTablesQuery({ projectRef: project?.ref, @@ -91,10 +93,6 @@ export const AuditLogsForm = () => { ) } - if (!canReadConfig) { - return - } - return (

diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx index 80284a6dec9f4..4a142cab825b9 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx @@ -14,7 +14,7 @@ import type { components } from 'data/api' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' import { Button, Form, Input, Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from 'ui' import { Admonition } from 'ui-patterns' @@ -38,7 +38,7 @@ export const ProviderForm = ({ config, provider, isActive }: ProviderFormProps) const [open, setOpen] = useState(false) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation() - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx index 5ee44c4ba4b7e..af36ff6689b6e 100644 --- a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx @@ -13,7 +13,7 @@ import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { AlertDescription_Shadcn_, @@ -54,11 +54,11 @@ export const BasicAuthSettingsForm = () => { } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation() - const { can: canReadConfig, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadConfig, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx index ae576925a302a..0ef99c3b567ed 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx @@ -11,7 +11,7 @@ import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useValidateSpamMutation, ValidateSpamResponse } from 'data/auth/validate-spam-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { FormSchema } from 'types' import { Button, @@ -40,7 +40,7 @@ interface TemplateEditorProps { const TemplateEditor = ({ template }: TemplateEditorProps) => { const { ref: projectRef } = useParams() - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx index 0cb2986486950..ed20cca8654ea 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx @@ -4,7 +4,7 @@ import { ChevronDown } from 'lucide-react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button, @@ -33,10 +33,7 @@ export const AddHookDropdown = ({ const { data: organization } = useSelectedOrganizationQuery() const { data: authConfig } = useAuthConfigQuery({ projectRef }) - const { can: canUpdateAuthHook } = useAsyncCheckProjectPermissions( - PermissionAction.AUTH_EXECUTE, - '*' - ) + const { can: canUpdateAuthHook } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') const hooks: Hook[] = HOOKS_DEFINITIONS.map((definition) => { return { diff --git a/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx b/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx index cf5b65112e04a..e09e75bf28001 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx @@ -3,7 +3,7 @@ import { Check, Webhook } from 'lucide-react' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DocsButton } from 'components/ui/DocsButton' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Badge, Input, copyToClipboard } from 'ui' import { Hook } from './hooks.constants' @@ -13,10 +13,7 @@ interface HookCardProps { } export const HookCard = ({ hook, onSelect }: HookCardProps) => { - const { can: canUpdateAuthHook } = useAsyncCheckProjectPermissions( - PermissionAction.AUTH_EXECUTE, - '*' - ) + const { can: canUpdateAuthHook } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') return (
diff --git a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx index 678e91226f71a..b6bf44c39bc42 100644 --- a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx @@ -11,7 +11,7 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { @@ -96,11 +96,11 @@ export const MfaAuthSettingsForm = () => { const [isConfirmationModalVisible, setIsConfirmationModalVisible] = useState(false) - const { can: canReadConfig } = useAsyncCheckProjectPermissions( + const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx index 57a9340330396..dc48f93087ba8 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx @@ -5,7 +5,7 @@ import { UseFormReturn } from 'react-hook-form' import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -62,7 +62,7 @@ export const PolicyDetailsV2 = ({ }: PolicyDetailsV2Props) => { const { data: project } = useSelectedProjectQuery() const [open, setOpen] = useState(false) - const { can: canUpdatePolicies } = useAsyncCheckProjectPermissions( + const { can: canUpdatePolicies } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx index ac3c2e99e3807..3ee605e2dbc7a 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx @@ -15,7 +15,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabasePolicyUpdateMutation } from 'data/database-policies/database-policy-update-mutation' import { databasePoliciesKeys } from 'data/database-policies/keys' import { QueryResponseError, useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -67,7 +67,7 @@ export const PolicyEditorPanel = memo(function ({ const queryClient = useQueryClient() const { data: selectedProject } = useSelectedProjectQuery() - const { can: canUpdatePolicies } = useAsyncCheckProjectPermissions( + const { can: canUpdatePolicies } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx index 953d628c44e1b..9f97fe8dc93a6 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx @@ -5,7 +5,7 @@ import { Edit, MoreVertical, Trash } from 'lucide-react' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { @@ -37,7 +37,7 @@ export const PolicyRow = ({ onSelectDeletePolicy = noop, }: PolicyRowProps) => { const aiSnap = useAiAssistantStateSnapshot() - const { can: canUpdatePolicies } = useAsyncCheckProjectPermissions( + const { can: canUpdatePolicies } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'policies' ) diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx index a4aa1911705b5..27fd57ac00a90 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx @@ -5,7 +5,7 @@ import { Lock, Table } from 'lucide-react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { AiIconAnimation, Badge, CardTitle } from 'ui' @@ -35,11 +35,11 @@ export const PolicyTableRowHeader = ({ const { ref } = useParams() const aiSnap = useAiAssistantStateSnapshot() - const { can: canCreatePolicies } = useAsyncCheckProjectPermissions( + const { can: canCreatePolicies } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'policies' ) - const { can: canToggleRLS } = useAsyncCheckProjectPermissions( + const { can: canToggleRLS } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx index 53bddf2acd937..791d1ccd6f1cf 100644 --- a/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx @@ -12,7 +12,7 @@ import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -80,11 +80,11 @@ export const ProtectionAuthSettingsForm = () => { }) const [hidden, setHidden] = useState(true) - const { can: canReadConfig } = useAsyncCheckProjectPermissions( + const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx b/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx index ade0bf2464113..82a5a1e14d920 100644 --- a/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx +++ b/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx @@ -13,7 +13,7 @@ import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Card, @@ -32,11 +32,11 @@ import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils' export const RateLimits = () => { const { ref: projectRef } = useParams() - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) - const { can: canReadConfig } = useAsyncCheckProjectPermissions( + const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx b/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx index fdcf729a56a9d..56b8644e1cb97 100644 --- a/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx +++ b/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx @@ -3,7 +3,7 @@ import { Globe, Trash } from 'lucide-react' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { EmptyListState } from 'components/ui/States' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Checkbox_Shadcn_ } from 'ui' import { ValueContainer } from './ValueContainer' @@ -24,7 +24,7 @@ export const RedirectUrlList = ({ onSelectRemoveURLs, onSelectClearSelection, }: RedirectUrlListProps) => { - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx index 0074539a98ca6..7ae7a7cc95825 100644 --- a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx @@ -11,7 +11,7 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { @@ -68,11 +68,11 @@ export const SessionsAuthSettingsForm = () => { const [isUpdatingRefreshTokens, setIsUpdatingRefreshTokens] = useState(false) const [isUpdatingUserSessions, setIsUpdatingUserSessions] = useState(false) - const { can: canReadConfig } = useAsyncCheckProjectPermissions( + const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx index fd8a8a5b8d705..b1fe49a1f550e 100644 --- a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx +++ b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx @@ -10,7 +10,7 @@ import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -36,7 +36,7 @@ const SiteUrl = () => { const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() const [isUpdatingSiteUrl, setIsUpdatingSiteUrl] = useState(false) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx index 70d041d7f3f26..01cf200b2c40a 100644 --- a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx +++ b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx @@ -12,7 +12,7 @@ import { ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -53,11 +53,11 @@ export const SmtpForm = () => { const [enableSmtp, setEnableSmtp] = useState(false) const [hidden, setHidden] = useState(true) - const { can: canReadConfig } = useAsyncCheckProjectPermissions( + const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx index 456bf706e53b7..3962304fe178f 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx @@ -17,7 +17,7 @@ import { ThirdPartyAuthIntegration, useThirdPartyAuthIntegrationsQuery, } from 'data/third-party-auth/integrations-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { cn } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { AddIntegrationDropdown } from './AddIntegrationDropdown' @@ -49,7 +49,7 @@ export const ThirdPartyAuthForm = () => { useState() const { mutateAsync: deleteIntegration } = useDeleteThirdPartyAuthIntegrationMutation() - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx index 2a7ef3f1bee35..afb88ae48cbfa 100644 --- a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -3,7 +3,7 @@ import { ChevronDown, Mail, UserPlus } from 'lucide-react' import { useState } from 'react' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from 'ui' import CreateUserModal from './CreateUserModal' @@ -12,11 +12,11 @@ import InviteUserModal from './InviteUserModal' export const AddUserDropdown = () => { const showSendInvitation = useIsFeatureEnabled('authentication:show_send_invitation') - const { can: canInviteUsers } = useAsyncCheckProjectPermissions( + const { can: canInviteUsers } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'invite_user' ) - const { can: canCreateUsers } = useAsyncCheckProjectPermissions( + const { can: canCreateUsers } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'create_user' ) diff --git a/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx b/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx index f2b630c369821..16ab8f410ffa6 100644 --- a/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx +++ b/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx @@ -7,7 +7,7 @@ import * as z from 'zod' import { useParams } from 'common' import { useUserCreateMutation } from 'data/auth/user-create-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Checkbox_Shadcn_, @@ -38,7 +38,7 @@ const CreateUserFormSchema = z.object({ const CreateUserModal = ({ visible, setVisible }: CreateUserModalProps) => { const { ref: projectRef } = useParams() - const { can: canCreateUsers } = useAsyncCheckProjectPermissions( + const { can: canCreateUsers } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'create_user' ) diff --git a/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx b/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx index d25657452053e..0fca24fe1d679 100644 --- a/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx +++ b/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx @@ -4,7 +4,7 @@ import { toast } from 'sonner' import { useParams } from 'common' import { useUserInviteMutation } from 'data/auth/user-invite-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Form, Input, Modal } from 'ui' export type InviteUserModalProps = { @@ -22,7 +22,7 @@ const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => { setVisible(false) }, }) - const { can: canInviteUsers } = useAsyncCheckProjectPermissions( + const { can: canInviteUsers } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'invite_user' ) diff --git a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx index 7737da47b31ba..c0d67fe41931e 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx @@ -15,7 +15,7 @@ import { useUserSendMagicLinkMutation } from 'data/auth/user-send-magic-link-mut import { useUserSendOTPMutation } from 'data/auth/user-send-otp-mutation' import { useUserUpdateMutation } from 'data/auth/user-update-mutation' import { User } from 'data/auth/users-infinite-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import { timeout } from 'lib/helpers' @@ -64,24 +64,21 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { } ) - const { can: canUpdateUser } = useAsyncCheckProjectPermissions(PermissionAction.AUTH_EXECUTE, '*') - const { can: canSendMagicLink } = useAsyncCheckProjectPermissions( + const { can: canUpdateUser } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') + const { can: canSendMagicLink } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'send_magic_link' ) - const { can: canSendRecovery } = useAsyncCheckProjectPermissions( + const { can: canSendRecovery } = useAsyncCheckPermissions( PermissionAction.AUTH_EXECUTE, 'send_recovery' ) - const { can: canSendOtp } = useAsyncCheckProjectPermissions( - PermissionAction.AUTH_EXECUTE, - 'send_otp' - ) - const { can: canRemoveUser } = useAsyncCheckProjectPermissions( + const { can: canSendOtp } = useAsyncCheckPermissions(PermissionAction.AUTH_EXECUTE, 'send_otp') + const { can: canRemoveUser } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_DELETE, 'auth.users' ) - const { can: canRemoveMFAFactors } = useAsyncCheckProjectPermissions( + const { can: canRemoveMFAFactors } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_DELETE, 'auth.mfa_factors' ) diff --git a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx index 31480f91c49b8..ca6c338b01556 100644 --- a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx +++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx @@ -6,7 +6,7 @@ import { useParams } from 'common' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button } from 'ui' import CreditCard from './CreditCard' @@ -31,7 +31,7 @@ const CurrentPaymentMethod = () => { const defaultPaymentMethod = paymentMethods?.data.find((pm) => pm.is_default) - const { can: canReadPaymentMethods } = useAsyncCheckProjectPermissions( + const { can: canReadPaymentMethods } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.payment_methods' ) diff --git a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx index 95d7d1b1076f6..e75c4a0ed2480 100644 --- a/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx +++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx @@ -19,7 +19,7 @@ import PartnerManagedResource from 'components/ui/PartnerManagedResource' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { MANAGED_BY } from 'lib/constants/infrastructure' import { getURL } from 'lib/helpers' @@ -44,9 +44,11 @@ const PaymentMethods = () => { isSuccess, } = useOrganizationPaymentMethodsQuery({ slug }) - const { can: canReadPaymentMethods, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.payment_methods') - const { can: canUpdatePaymentMethods } = useAsyncCheckProjectPermissions( + const { can: canReadPaymentMethods, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.payment_methods' + ) + const { can: canUpdatePaymentMethods } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.payment_methods' ) diff --git a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx index 6ca4120b6c9fc..668a627bf0a20 100644 --- a/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx +++ b/apps/studio/components/interfaces/BranchManagement/CreateBranchModal.tsx @@ -27,7 +27,7 @@ import { projectKeys } from 'data/projects/keys' import { DesiredInstanceSize, instanceSizeSpecs } from 'data/projects/new-project.constants' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { BASE_PATH, IS_PLATFORM } from 'lib/constants' @@ -72,7 +72,7 @@ export const CreateBranchModal = () => { const gitlessBranching = useIsBranching2Enabled() const allowDataBranching = useFlag('allowDataBranching') - const { can: canCreateBranch } = useAsyncCheckProjectPermissions( + const { can: canCreateBranch } = useAsyncCheckPermissions( PermissionAction.CREATE, 'preview_branches' ) diff --git a/apps/studio/components/interfaces/BranchManagement/Overview.tsx b/apps/studio/components/interfaces/BranchManagement/Overview.tsx index 88257f88afda3..887ada063ae7d 100644 --- a/apps/studio/components/interfaces/BranchManagement/Overview.tsx +++ b/apps/studio/components/interfaces/BranchManagement/Overview.tsx @@ -23,7 +23,7 @@ import { useBranchResetMutation } from 'data/branches/branch-reset-mutation' import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import type { Branch } from 'data/branches/branches-query' import { branchKeys } from 'data/branches/keys' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, DropdownMenu, @@ -169,11 +169,11 @@ const PreviewBranchActions = ({ const queryClient = useQueryClient() const projectRef = branch.parent_project_ref ?? branch.project_ref - const { can: canDeleteBranches } = useAsyncCheckProjectPermissions( + const { can: canDeleteBranches } = useAsyncCheckPermissions( PermissionAction.DELETE, 'preview_branches' ) - const { can: canUpdateBranches } = useAsyncCheckProjectPermissions( + const { can: canUpdateBranches } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'preview_branches' ) @@ -387,7 +387,7 @@ const PreviewBranchActions = ({ // Actions for main (production) branch const MainBranchActions = ({ branch, repo }: { branch: Branch; repo: string }) => { const { ref: projectRef } = useParams() - const { can: canUpdateBranches } = useAsyncCheckProjectPermissions( + const { can: canUpdateBranches } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'preview_branches' ) diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx index 7a816671e1193..7a6f1e2133358 100644 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ b/apps/studio/components/interfaces/Connect/Connect.tsx @@ -9,7 +9,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import Panel from 'components/ui/Panel' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' @@ -99,7 +99,7 @@ export const Connect = () => { ) const { data: settings } = useProjectSettingsV2Query({ projectRef }, { enabled: showConnect }) - const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' ) diff --git a/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx b/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx index dbe0a9ca5e440..85a69d5680e9b 100644 --- a/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx +++ b/apps/studio/components/interfaces/Database/Backups/BackupItem.tsx @@ -6,7 +6,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { InlineLink } from 'components/ui/InlineLink' import { useBackupDownloadMutation } from 'data/database/backup-download-mutation' import type { DatabaseBackup } from 'data/database/backups-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Badge, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { TimestampInfo } from 'ui-patterns' @@ -19,7 +19,7 @@ interface BackupItemProps { export const BackupItem = ({ index, isHealthy, backup, onSelectBackup }: BackupItemProps) => { const { ref: projectRef } = useParams() - const { can: canTriggerScheduledBackups } = useAsyncCheckProjectPermissions( + const { can: canTriggerScheduledBackups } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.restore.prepare' ) diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx index 33a4d11e0b816..05b8faf99466d 100644 --- a/apps/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx +++ b/apps/studio/components/interfaces/Database/Backups/PITR/PITRNotice.tsx @@ -6,7 +6,7 @@ import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FormPanel } from 'components/ui/Forms/FormPanel' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { getPITRRetentionDuration } from './PITR.utils' const PITRNotice = ({}) => { @@ -14,7 +14,7 @@ const PITRNotice = ({}) => { const { data: addonsResponse } = useProjectAddonsQuery({ projectRef }) const retentionPeriod = getPITRRetentionDuration(addonsResponse?.selected_addons ?? []) - const { can: canUpdateSubscription } = useAsyncCheckProjectPermissions( + const { can: canUpdateSubscription } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx index 0f77410269344..763ede84c069e 100644 --- a/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx +++ b/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx @@ -7,7 +7,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FormPanel } from 'components/ui/Forms/FormPanel' import { useBackupsQuery } from 'data/database/backups-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { Timezone } from './PITR.types' import { TimezoneSelection } from './TimezoneSelection' @@ -41,7 +41,7 @@ const PITRStatus = ({ .tz(selectedTimezone?.utc[0]) .format('DD MMM YYYY, HH:mm:ss') - const { can: canTriggerPhysicalBackup } = useAsyncCheckProjectPermissions( + const { can: canTriggerPhysicalBackup } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.walg.prepare_restore' ) diff --git a/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx b/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx index 45e3985ef839e..3139f8286e776 100644 --- a/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx +++ b/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx @@ -7,7 +7,7 @@ import { toast } from 'sonner' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabaseExtensionDisableMutation } from 'data/database-extensions/database-extension-disable-mutation' import { DatabaseExtension } from 'data/database-extensions/database-extensions-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { extensions } from 'shared-data' import { Button, Switch, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui' @@ -28,7 +28,7 @@ export const ExtensionRow = ({ extension }: ExtensionRowProps) => { const [isDisableModalOpen, setIsDisableModalOpen] = useState(false) const [showConfirmEnableModal, setShowConfirmEnableModal] = useState(false) - const { can: canUpdateExtensions } = useAsyncCheckProjectPermissions( + const { can: canUpdateExtensions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions' ) diff --git a/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx b/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx index 3e170bcd7f03a..0885380baa284 100644 --- a/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx +++ b/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx @@ -9,7 +9,7 @@ import InformationBox from 'components/ui/InformationBox' import NoSearchResults from 'components/ui/NoSearchResults' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Card, @@ -51,8 +51,10 @@ export const Extensions = () => { (ext) => !isNull(ext.installed_version) ) - const { can: canUpdateExtensions, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions') + const { can: canUpdateExtensions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'extensions' + ) useEffect(() => { if (filter !== undefined) setFilterString(filter as string) diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx index 680755b6c3263..b2e7d008db22c 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionList.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/router' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabaseFunctionsQuery } from 'data/database-functions/database-functions-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { @@ -15,8 +15,8 @@ import { DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, - TableRow, TableCell, + TableRow, } from 'ui' interface FunctionListProps { @@ -51,7 +51,7 @@ const FunctionList = ({ (func) => func.name.toLocaleLowerCase() ) const projectRef = selectedProject?.ref - const { can: canUpdateFunctions } = useAsyncCheckProjectPermissions( + const { can: canUpdateFunctions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'functions' ) diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index bc8f73b826ac5..82ca681b97347 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -12,21 +12,20 @@ import SchemaSelector from 'components/ui/SchemaSelector' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabaseFunctionsQuery } from 'data/database-functions/database-functions-query' import { useSchemasQuery } from 'data/database/schemas-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { AiIconAnimation, + Card, Input, Table, - TableHeader, - TableHead, TableBody, + TableHead, + TableHeader, TableRow, - TableCell, - Card, } from 'ui' import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning' import FunctionList from './FunctionList' @@ -60,7 +59,7 @@ const FunctionsList = ({ router.push(url) } - const { can: canCreateFunctions } = useAsyncCheckProjectPermissions( + const { can: canCreateFunctions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'functions' ) diff --git a/apps/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx b/apps/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx index c259acb39d0c5..5ef24ae87f65f 100644 --- a/apps/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx +++ b/apps/studio/components/interfaces/Database/Hooks/HooksList/HookList.tsx @@ -8,7 +8,7 @@ import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabaseHooksQuery } from 'data/database-triggers/database-triggers-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { BASE_PATH } from 'lib/constants' import { @@ -50,7 +50,7 @@ export const HookList = ({ x.schema === schema && x.function_args.length >= 2 ) - const { can: canUpdateWebhook } = useAsyncCheckProjectPermissions( + const { can: canUpdateWebhook } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers' ) diff --git a/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx b/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx index a53ac137264b8..7e43bf126ace6 100644 --- a/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx +++ b/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx @@ -10,7 +10,7 @@ import { DocsButton } from 'components/ui/DocsButton' import NoSearchResults from 'components/ui/NoSearchResults' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabaseHooksQuery } from 'data/database-triggers/database-triggers-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { noop } from 'lib/void' import { Input } from 'ui' @@ -46,8 +46,10 @@ export const HooksList = ({ ) const filteredHookSchemas = lodashMap(uniqBy(filteredHooks, 'schema'), 'schema') - const { can: canCreateWebhooks, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers') + const { can: canCreateWebhooks, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'triggers' + ) return (
diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx index deb947dd863c9..6bb04e304d198 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsList.tsx @@ -10,7 +10,7 @@ import InformationBox from 'components/ui/InformationBox' import NoSearchResults from 'components/ui/NoSearchResults' import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query' import { useDatabasePublicationUpdateMutation } from 'data/database-publications/database-publications-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -57,8 +57,10 @@ export const PublicationsList = () => { }, }) - const { can: canUpdatePublications, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'publications') + const { can: canUpdatePublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'publications' + ) const publicationEvents: PublicationEvent[] = [ { event: 'Insert', key: 'publish_insert' }, diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx index b7a8425e0026d..c5aec787a996d 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsTableItem.tsx @@ -4,7 +4,7 @@ import { useState } from 'react' import { toast } from 'sonner' import { useDatabasePublicationUpdateMutation } from 'data/database-publications/database-publications-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProtectedSchemas } from 'hooks/useProtectedSchemas' import { Badge, Switch, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui' @@ -28,7 +28,7 @@ export const PublicationsTableItem = ({ selectedPublication.tables?.find((x: any) => x.id == table.id) != undefined ) - const { can: canUpdatePublications } = useAsyncCheckProjectPermissions( + const { can: canUpdatePublications } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'publications' ) diff --git a/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx b/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx index fe55650d79a6b..48931a50560d1 100644 --- a/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx +++ b/apps/studio/components/interfaces/Database/Publications/PublicationsTables.tsx @@ -10,7 +10,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { Loading } from 'components/ui/Loading' import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { Admonition } from 'ui-patterns' @@ -22,8 +22,10 @@ export const PublicationsTables = () => { const { data: project } = useSelectedProjectQuery() const [filterString, setFilterString] = useState('') - const { can: canUpdatePublications, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'publications') + const { can: canUpdatePublications, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'publications' + ) const { data: publications = [] } = useDatabasePublicationsQuery({ projectRef: project?.ref, diff --git a/apps/studio/components/interfaces/Database/Roles/RolesList.tsx b/apps/studio/components/interfaces/Database/Roles/RolesList.tsx index 1385c807f34a1..f18ec8b90647e 100644 --- a/apps/studio/components/interfaces/Database/Roles/RolesList.tsx +++ b/apps/studio/components/interfaces/Database/Roles/RolesList.tsx @@ -8,7 +8,7 @@ import NoSearchResults from 'components/ui/NoSearchResults' import SparkBar from 'components/ui/SparkBar' import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' import { useMaxConnectionsQuery } from 'data/database/max-connections-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Badge, Button, Input, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { CreateRolePanel } from './CreateRolePanel' @@ -27,7 +27,7 @@ const RolesList = () => { const [isCreatingRole, setIsCreatingRole] = useState(false) const [selectedRoleToDelete, setSelectedRoleToDelete] = useState() - const { can: canUpdateRoles } = useAsyncCheckProjectPermissions( + const { can: canUpdateRoles } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'roles' ) diff --git a/apps/studio/components/interfaces/Database/Tables/ColumnList.tsx b/apps/studio/components/interfaces/Database/Tables/ColumnList.tsx index 83aaf0cacef96..a4e9c23bca2e4 100644 --- a/apps/studio/components/interfaces/Database/Tables/ColumnList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/ColumnList.tsx @@ -13,7 +13,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useTableEditorQuery } from 'data/table-editor/table-editor-query' import { isTableLike } from 'data/table-editor/table-editor-types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import { @@ -65,7 +65,7 @@ export const ColumnList = ({ : selectedTable?.columns?.filter((column) => column.name.includes(filterString))) ?? [] const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedTable?.schema ?? '' }) - const { can: canUpdateColumns } = useAsyncCheckProjectPermissions( + const { can: canUpdateColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) diff --git a/apps/studio/components/interfaces/Database/Tables/TableList.tsx b/apps/studio/components/interfaces/Database/Tables/TableList.tsx index 5d08596a5218a..a54046da943fb 100644 --- a/apps/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/apps/studio/components/interfaces/Database/Tables/TableList.tsx @@ -32,7 +32,7 @@ import { useMaterializedViewsQuery } from 'data/materialized-views/materialized- import { usePrefetchEditorTablePage } from 'data/prefetchers/project.$ref.editor.$id' import { useTablesQuery } from 'data/tables/tables-query' import { useViewsQuery } from 'data/views/views-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' @@ -50,15 +50,15 @@ import { PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_, - Tooltip, - TooltipContent, - TooltipTrigger, Table, - TableHeader, - TableHead, TableBody, - TableRow, TableCell, + TableHead, + TableHeader, + TableRow, + Tooltip, + TooltipContent, + TooltipTrigger, cn, } from 'ui' import { ProtectedSchemaWarning } from '../ProtectedSchemaWarning' @@ -88,7 +88,7 @@ export const TableList = ({ const [filterString, setFilterString] = useState('') const [visibleTypes, setVisibleTypes] = useState(Object.values(ENTITY_TYPE)) - const { can: canUpdateTables } = useAsyncCheckProjectPermissions( + const { can: canUpdateTables } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx index f78e526252bc5..bb2fede7e02d0 100644 --- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx +++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggerList.tsx @@ -2,10 +2,9 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { includes, sortBy } from 'lodash' import { Check, Edit, Edit2, MoreVertical, Trash, X } from 'lucide-react' -import Table from 'components/to-be-cleaned/Table' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabaseTriggersQuery } from 'data/database-triggers/database-triggers-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { @@ -15,11 +14,11 @@ import { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + TableCell, + TableRow, Tooltip, TooltipContent, TooltipTrigger, - TableRow, - TableCell, } from 'ui' import { generateTriggerCreateSQL } from './TriggerList.utils' @@ -53,7 +52,7 @@ const TriggerList = ({ filteredTriggers.filter((x) => x.schema == schema), (trigger) => trigger.name.toLocaleLowerCase() ) - const { can: canUpdateTriggers } = useAsyncCheckProjectPermissions( + const { can: canUpdateTriggers } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers' ) diff --git a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx index a9097f7190d0a..3d6f2287f0a1b 100644 --- a/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx +++ b/apps/studio/components/interfaces/Database/Triggers/TriggersList/TriggersList.tsx @@ -12,21 +12,20 @@ import SchemaSelector from 'components/ui/SchemaSelector' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabaseTriggersQuery } from 'data/database-triggers/database-triggers-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema, useProtectedSchemas } from 'hooks/useProtectedSchemas' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { AiIconAnimation, - Input, Card, + Input, Table, - TableHeader, - TableHead, TableBody, + TableHead, + TableHeader, TableRow, - TableCell, } from 'ui' import { ProtectedSchemaWarning } from '../../ProtectedSchemaWarning' import TriggerList from './TriggerList' @@ -67,7 +66,7 @@ const TriggersList = ({ connectionString: project?.connectionString, }) - const { can: canCreateTriggers } = useAsyncCheckProjectPermissions( + const { can: canCreateTriggers } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers' ) diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx index 8cb6d6c6ee065..cf0757aee7908 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementForm.tsx @@ -25,7 +25,7 @@ import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon- import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { AddonVariantId } from 'data/subscriptions/types' import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useIsAwsCloudProvider, @@ -80,7 +80,7 @@ export function DiskManagementForm() { const isAwsK8s = useIsAwsK8sCloudProvider() const { can: canUpdateDiskConfiguration, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.UPDATE, 'projects', { + useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { resource: { project_id: project?.id, }, diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx index 89512705ff584..ec8d3ef146d6e 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagementReviewAndSubmitDialog.tsx @@ -5,7 +5,7 @@ import { UseFormReturn } from 'react-hook-form' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { formatCurrency } from 'lib/helpers' @@ -153,7 +153,7 @@ export const DiskManagementReviewAndSubmitDialog = ({ const { formState, getValues } = form - const { can: canUpdateDiskConfiguration } = useAsyncCheckProjectPermissions( + const { can: canUpdateDiskConfiguration } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Docs/Description.tsx b/apps/studio/components/interfaces/Docs/Description.tsx index b2e6176400fdc..06dad2b652a60 100644 --- a/apps/studio/components/interfaces/Docs/Description.tsx +++ b/apps/studio/components/interfaces/Docs/Description.tsx @@ -5,7 +5,7 @@ import { toast } from 'sonner' import AutoTextArea from 'components/to-be-cleaned/forms/AutoTextArea' import { executeSql } from 'data/sql/execute-sql-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { timeout } from 'lib/helpers' import { Loader } from 'lucide-react' @@ -42,7 +42,7 @@ const Description = ({ content, metadata, onChange = noop }: DescrptionProps) => const hasChanged = value != contentText const animateCss = `transition duration-150` - const { can: canUpdateDescription } = useAsyncCheckProjectPermissions( + const { can: canUpdateDescription } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_QUERY, '*' ) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index efde692501c20..d4b163cfee413 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -18,7 +18,7 @@ import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation' import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-functions-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Alert_Shadcn_, AlertDescription_Shadcn_, @@ -59,7 +59,7 @@ export const EdgeFunctionDetails = () => { const router = useRouter() const { ref: projectRef, functionSlug } = useParams() const [showDeleteModal, setShowDeleteModal] = useState(false) - const { can: canUpdateEdgeFunction } = useAsyncCheckProjectPermissions( + const { can: canUpdateEdgeFunction } = useAsyncCheckPermissions( PermissionAction.FUNCTIONS_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx index 16edc741a3f8e..9e9aaf20ecc28 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecret.tsx @@ -3,7 +3,7 @@ import { Trash } from 'lucide-react' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import type { ProjectSecret } from 'data/secrets/secrets-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { TableCell, TableRow } from 'ui' import { TimestampInfo } from 'ui-patterns' @@ -13,10 +13,7 @@ interface EdgeFunctionSecretProps { } const EdgeFunctionSecret = ({ secret, onSelectDelete }: EdgeFunctionSecretProps) => { - const { can: canUpdateSecrets } = useAsyncCheckProjectPermissions( - PermissionAction.SECRETS_WRITE, - '*' - ) + const { can: canUpdateSecrets } = useAsyncCheckPermissions(PermissionAction.SECRETS_WRITE, '*') // [Joshen] Following API's validation: // https://github.com/supabase/infrastructure/blob/develop/api/src/routes/v1/projects/ref/secrets/secrets.controller.ts#L106 const isReservedSecret = !!secret.name.match(/^(SUPABASE_).*/) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx index 00d859249f969..01938c432b6b7 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionSecrets/EdgeFunctionSecrets.tsx @@ -9,7 +9,7 @@ import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useSecretsDeleteMutation } from 'data/secrets/secrets-delete-mutation' import { ProjectSecret, useSecretsQuery } from 'data/secrets/secrets-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Badge, Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' @@ -21,14 +21,11 @@ const EdgeFunctionSecrets = () => { const [searchString, setSearchString] = useState('') const [selectedSecret, setSelectedSecret] = useState() - const { can: canReadSecrets, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions( + const { can: canReadSecrets, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.SECRETS_READ, '*' ) - const { can: canUpdateSecrets } = useAsyncCheckProjectPermissions( - PermissionAction.SECRETS_WRITE, - '*' - ) + const { can: canUpdateSecrets } = useAsyncCheckPermissions(PermissionAction.SECRETS_WRITE, '*') const { data, error, isLoading, isSuccess, isError } = useSecretsQuery({ projectRef: projectRef, diff --git a/apps/studio/components/interfaces/GraphQL/GraphiQL.tsx b/apps/studio/components/interfaces/GraphQL/GraphiQL.tsx index c7351a4db71e2..7d64ac69c162c 100644 --- a/apps/studio/components/interfaces/GraphQL/GraphiQL.tsx +++ b/apps/studio/components/interfaces/GraphQL/GraphiQL.tsx @@ -36,7 +36,7 @@ import { AlertTriangle, XIcon } from 'lucide-react' import { MouseEventHandler, useCallback, useEffect, useState } from 'react' import { LOCAL_STORAGE_KEYS } from 'common' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useLocalStorage } from 'hooks/misc/useLocalStorage' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, cn } from 'ui' import { RoleImpersonationSelector } from '../RoleImpersonationSelector' @@ -76,7 +76,7 @@ const GraphiQLInterface = ({ theme }: GraphiQLInterfaceProps) => { const merge = useMergeQuery() const prettify = usePrettifyEditors() - const { can: canReadJWTSecret } = useAsyncCheckProjectPermissions( + const { can: canReadJWTSecret } = useAsyncCheckPermissions( PermissionAction.READ, 'field.jwt_secret' ) diff --git a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx index 438cbd5fd70e6..60298fafa9d9d 100644 --- a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx +++ b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx @@ -9,7 +9,7 @@ import Panel from 'components/ui/Panel' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { Input, SimpleCodeBlock } from 'ui' @@ -77,7 +77,7 @@ export const APIKeys = () => { const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus - const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' ) diff --git a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx index 19f479ada64ba..00e487c2052f4 100644 --- a/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx +++ b/apps/studio/components/interfaces/HomeNew/CustomReportSection.tsx @@ -12,6 +12,7 @@ import dayjs from 'dayjs' import { Plus } from 'lucide-react' import type { CSSProperties, ReactNode } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react' +import { toast } from 'sonner' import { useParams } from 'common' import { SnippetDropdown } from 'components/interfaces/HomeNew/SnippetDropdown' @@ -22,13 +23,12 @@ import { AnalyticsInterval } from 'data/analytics/constants' import { useContentInfiniteQuery } from 'data/content/content-infinite-query' import { Content } from 'data/content/content-query' import { useContentUpsertMutation } from 'data/content/content-upsert-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import type { Dashboards } from 'types' import { Button } from 'ui' import { Row } from 'ui-patterns' -import { toast } from 'sonner' export function CustomReportSection() { const startDate = dayjs().subtract(7, 'day').toISOString() @@ -50,7 +50,7 @@ export function CustomReportSection() { if (reportContent) setEditableReport(reportContent) }, [reportContent]) - const { can: canUpdateReport } = useAsyncCheckProjectPermissions( + const { can: canUpdateReport } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'user_content', { @@ -63,7 +63,7 @@ export function CustomReportSection() { } ) - const { can: canCreateReport } = useAsyncCheckProjectPermissions( + const { can: canCreateReport } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { resource: { type: 'report', owner_id: profile?.id }, subject: { id: profile?.id } } diff --git a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx index 1d3072b5a9096..e7a42f67760fd 100644 --- a/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx +++ b/apps/studio/components/interfaces/Integrations/CronJobs/CreateCronJobSheet.tsx @@ -17,7 +17,7 @@ import { useDatabaseCronJobCreateMutation } from 'data/database-cron-jobs/databa import { CronJob } from 'data/database-cron-jobs/database-cron-jobs-infinite-query' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { @@ -223,7 +223,7 @@ export const CreateCronJobSheet = ({ const { mutate: upsertCronJob, isLoading: isUpserting } = useDatabaseCronJobCreateMutation() const isLoading = isLoadingGetCronJob || isUpserting - const { can: canToggleExtensions } = useAsyncCheckProjectPermissions( + const { can: canToggleExtensions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions' ) diff --git a/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx b/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx index 2b5b6bd079284..eefac54699b47 100644 --- a/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx +++ b/apps/studio/components/interfaces/Integrations/Queues/QueuesSettings.tsx @@ -21,7 +21,7 @@ import { } from 'data/database-queues/database-queues-toggle-postgrest-mutation' import { useTableUpdateMutation } from 'data/tables/table-update-mutation' import { useTablesQuery } from 'data/tables/tables-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -39,7 +39,7 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' export const QueuesSettings = () => { const { data: project } = useSelectedProjectQuery() - const { can: canUpdatePostgrestConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdatePostgrestConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_postgrest' ) diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx index d15afd50960b0..b0b738b858e6d 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretRow.tsx @@ -12,7 +12,7 @@ import { import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Edit3, Eye, EyeOff, Key, Loader, MoreVertical, Trash } from 'lucide-react' import type { VaultSecret } from 'types' @@ -33,7 +33,7 @@ const SecretRow = ({ row, col, onSelectRemove }: SecretRowProps) => { const [revealSecret, setRevealSecret] = useState(false) const name = row?.name ?? 'No name provided' - const { can: canManageSecrets } = useAsyncCheckProjectPermissions( + const { can: canManageSecrets } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx index 05bd8a8f7264f..7a0575c6da2f3 100644 --- a/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx +++ b/apps/studio/components/interfaces/Integrations/Vault/Secrets/SecretsManagement.tsx @@ -1,26 +1,26 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import DataGrid, { Row } from 'react-data-grid' import { sortBy } from 'lodash' -import { Loader, RefreshCw, Search, X } from 'lucide-react' +import { RefreshCw, Search, X } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' +import DataGrid, { Row } from 'react-data-grid' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DocsButton } from 'components/ui/DocsButton' import { useVaultSecretsQuery } from 'data/vault/vault-secrets-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import type { VaultSecret } from 'types' import { Button, + cn, Input, LoadingLine, - cn, Select_Shadcn_, - SelectTrigger_Shadcn_, - SelectValue_Shadcn_, SelectContent_Shadcn_, SelectItem_Shadcn_, + SelectTrigger_Shadcn_, + SelectValue_Shadcn_, } from 'ui' import AddNewSecretModal from './AddNewSecretModal' import DeleteSecretModal from './DeleteSecretModal' @@ -35,7 +35,7 @@ export const SecretsManagement = () => { const [selectedSecretToRemove, setSelectedSecretToRemove] = useState() const [selectedSort, setSelectedSort] = useState<'updated_at' | 'name'>('updated_at') - const { can: canManageSecrets } = useAsyncCheckProjectPermissions( + const { can: canManageSecrets } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/interfaces/Integrations/Webhooks/ListTab.tsx b/apps/studio/components/interfaces/Integrations/Webhooks/ListTab.tsx index 2e87e67a2092d..f5dce7cde94fd 100644 --- a/apps/studio/components/interfaces/Integrations/Webhooks/ListTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Webhooks/ListTab.tsx @@ -6,14 +6,14 @@ import DeleteHookModal from 'components/interfaces/Database/Hooks/DeleteHookModa import { EditHookPanel } from 'components/interfaces/Database/Hooks/EditHookPanel' import { HooksList } from 'components/interfaces/Database/Hooks/HooksList/HooksList' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' export const WebhooksListTab = () => { const [selectedHook, setSelectedHook] = useState() const [showCreateHookForm, setShowCreateHookForm] = useState(false) const [showDeleteHookForm, setShowDeleteHookForm] = useState(false) - const { can: canReadWebhooks, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadWebhooks, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers' ) diff --git a/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx index a1efc6d7310f9..883823083cc30 100644 --- a/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Webhooks/OverviewTab.tsx @@ -7,7 +7,7 @@ import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useHooksEnableMutation } from 'data/database/hooks-enable-mutation' import { useSchemasQuery } from 'data/database/schemas-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Admonition } from 'ui-patterns' import { IntegrationOverviewTab } from '../Integration/IntegrationOverviewTab' @@ -26,7 +26,7 @@ export const WebhooksOverviewTab = () => { }) const isHooksEnabled = schemas?.some((schema) => schema.name === 'supabase_functions') - const { can: canReadWebhooks, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions( + const { can: canReadWebhooks, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers' ) diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx index dd923c8c728e9..6d422b8dfe8aa 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/OverviewTab.tsx @@ -5,7 +5,7 @@ import { useState } from 'react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Alert_Shadcn_, @@ -28,7 +28,7 @@ export const WrapperOverviewTab = () => { const [createWrapperShown, setCreateWrapperShown] = useState(false) const [isClosingCreateWrapper, setisClosingCreateWrapper] = useState(false) - const { can: canCreateWrapper } = useAsyncCheckProjectPermissions( + const { can: canCreateWrapper } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers' ) diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx index f64438c7d7d9d..5f830a4acf0b6 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx @@ -7,7 +7,7 @@ import { useState } from 'react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import type { FDW } from 'data/fdw/fdws-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Badge, Sheet, @@ -29,7 +29,7 @@ interface WrapperRowProps { const WrapperRow = ({ wrapper }: WrapperRowProps) => { const { ref, id } = useParams() - const { can: canManageWrappers } = useAsyncCheckProjectPermissions( + const { can: canManageWrappers } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers' ) diff --git a/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx index f9669f4a8ab03..3c73661275c12 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/WrappersTab.tsx @@ -4,7 +4,7 @@ import { HTMLProps, ReactNode, useCallback, useState } from 'react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { FDW, useFDWsQuery } from 'data/fdw/fdws-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Sheet, SheetContent } from 'ui' import { CreateWrapperSheet } from './CreateWrapperSheet' @@ -20,7 +20,7 @@ export const WrappersTab = () => { const [createWrapperShown, setCreateWrapperShown] = useState(false) const [isClosingCreateWrapper, setisClosingCreateWrapper] = useState(false) - const { can: canCreateWrapper } = useAsyncCheckProjectPermissions( + const { can: canCreateWrapper } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'wrappers' ) diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx index 0f5a4b34ae057..28458b0b70444 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-settings.tsx @@ -16,7 +16,7 @@ import { useJwtSecretUpdateMutation } from 'data/config/jwt-secret-update-mutati import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query' import { useLegacyJWTSigningKeyQuery } from 'data/jwt-signing-keys/legacy-jwt-signing-key-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { uuidv4 } from 'lib/helpers' import { AlertCircle, @@ -73,15 +73,15 @@ const JWTSettings = () => { const [isCreatingKey, setIsCreatingKey] = useState(false) const [isRegeneratingKey, setIsGeneratingKey] = useState(false) - const { can: canReadJWTSecret } = useAsyncCheckProjectPermissions( + const { can: canReadJWTSecret } = useAsyncCheckPermissions( PermissionAction.READ, 'field.jwt_secret' ) - const { can: canGenerateNewJWTSecret } = useAsyncCheckProjectPermissions( + const { can: canGenerateNewJWTSecret } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.projects.update_jwt' ) - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx index f6e1fa8bc1779..e4b49eecc6ea5 100644 --- a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx +++ b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx @@ -23,7 +23,7 @@ import { import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -54,8 +54,10 @@ export const AuditLogs = () => { projects: [], // project_ref[] }) - const { can: canReadAuditLogs, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'notifications') + const { can: canReadAuditLogs, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.READ, + 'notifications' + ) const { data: projectsData } = useProjectsQuery() const projects = projectsData?.projects ?? [] diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx index 0195c78d2e931..9bbcfec1b4c75 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingBreakdown/BillingBreakdown.tsx @@ -13,19 +13,21 @@ import NoPermission from 'components/ui/NoPermission' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import SparkBar from 'components/ui/SparkBar' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' -import UpcomingInvoice from './UpcomingInvoice' -import { MANAGED_BY } from 'lib/constants/infrastructure' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' +import { MANAGED_BY } from 'lib/constants/infrastructure' +import UpcomingInvoice from './UpcomingInvoice' const BillingBreakdown = () => { const { slug: orgSlug } = useParams() const { data: selectedOrganization } = useSelectedOrganizationQuery() - const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const { data: subscription, diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx index 073177cae61dc..3d18d05d427af 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingCustomerData/BillingCustomerData.tsx @@ -16,7 +16,7 @@ import { useOrganizationCustomerProfileQuery } from 'data/organizations/organiza import { useOrganizationCustomerProfileUpdateMutation } from 'data/organizations/organization-customer-profile-update-mutation' import { useOrganizationTaxIdQuery } from 'data/organizations/organization-tax-id-query' import { useOrganizationTaxIdUpdateMutation } from 'data/organizations/organization-tax-id-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button, Card, CardFooter, Form_Shadcn_ as Form } from 'ui' import { @@ -31,8 +31,8 @@ export const BillingCustomerData = () => { const { data: selectedOrganization } = useSelectedOrganizationQuery() const { can: canReadBillingCustomerData, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.customer') - const { can: canUpdateBillingCustomerData } = useAsyncCheckProjectPermissions( + useAsyncCheckPermissions(PermissionAction.BILLING_READ, 'stripe.customer') + const { can: canUpdateBillingCustomerData } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.customer' ) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx index 2864351ff57ef..a1c57c8eee75f 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/BillingEmail.tsx @@ -18,7 +18,7 @@ import { FormSection, FormSectionContent } from 'components/ui/Forms/FormSection import NoPermission from 'components/ui/NoPermission' import { useOrganizationCustomerProfileQuery } from 'data/organizations/organization-customer-profile-query' import { useOrganizationUpdateMutation } from 'data/organizations/organization-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { FormMessage_Shadcn_, Input_Shadcn_ } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' @@ -42,9 +42,11 @@ const BillingEmail = () => { const { name, billing_email } = selectedOrganization ?? {} - const { can: canReadBillingEmail, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') - const { can: canUpdateOrganization } = useAsyncCheckProjectPermissions( + const { can: canReadBillingEmail, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) + const { can: canUpdateOrganization } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx index c021252d87fc4..652d796055570 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/CostControl.tsx @@ -15,7 +15,7 @@ import NoPermission from 'components/ui/NoPermission' import { PARTNER_TO_NAME } from 'components/ui/PartnerManagedResource' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { BASE_PATH } from 'lib/constants' import { MANAGED_BY } from 'lib/constants/infrastructure' @@ -33,8 +33,10 @@ const CostControl = ({}: CostControlProps) => { const { resolvedTheme } = useTheme() const { data: selectedOrganization } = useSelectedOrganizationQuery() - const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const snap = useOrgSettingsPageStateSnapshot() const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx index 63588dedf5fc8..367a5480ec65e 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CostControl/SpendCapSidePanel.tsx @@ -9,7 +9,7 @@ import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useOrgSubscriptionUpdateMutation } from 'data/subscriptions/org-subscription-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH, PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import { ChevronRight, ExternalLink } from 'lucide-react' import { pricing } from 'shared-data/pricing' @@ -43,7 +43,7 @@ const SpendCapSidePanel = () => { const [showUsageCosts, setShowUsageCosts] = useState(false) const [selectedOption, setSelectedOption] = useState<'on' | 'off'>() - const { can: canUpdateSpendCap } = useAsyncCheckProjectPermissions( + const { can: canUpdateSpendCap } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx index cfb8a013dfff7..bfc48ccab374e 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx @@ -11,14 +11,16 @@ import { FormPanel } from 'components/ui/Forms/FormPanel' import { FormSection, FormSectionContent } from 'components/ui/Forms/FormSection' import NoPermission from 'components/ui/NoPermission' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { CreditTopUp } from './CreditTopUp' const CreditBalance = () => { const { slug } = useParams() - const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const { data: subscription, diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx index 094f62f63aa5c..75a58613e08bc 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx @@ -17,7 +17,7 @@ import { PaymentConfirmation } from 'components/interfaces/Billing/Payment/Payme import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useOrganizationCreditTopUpMutation } from 'data/organizations/organization-credit-top-up-mutation' import { subscriptionKeys } from 'data/subscriptions/keys' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { STRIPE_PUBLIC_KEY } from 'lib/constants' import { Alert_Shadcn_, @@ -63,7 +63,7 @@ export const CreditTopUp = ({ slug }: { slug: string | undefined }) => { createPaymentMethod: PaymentMethodElementRef['createPaymentMethod'] }>(null) - const { can: canTopUpCredits, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canTopUpCredits, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx index 002dd26dbe0b2..5d1f473e0b1bf 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/PlanUpdateSidePanel.tsx @@ -18,7 +18,7 @@ import { useOrgPlansQuery } from 'data/subscriptions/org-plans-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import type { OrgPlan } from 'data/subscriptions/types' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { MANAGED_BY } from 'lib/constants/infrastructure' import { formatCurrency } from 'lib/helpers' @@ -60,7 +60,7 @@ const PlanUpdateSidePanel = () => { const [showDowngradeError, setShowDowngradeError] = useState(false) const [selectedTier, setSelectedTier] = useState<'tier_free' | 'tier_pro' | 'tier_team'>() - const { can: canUpdateSubscription } = useAsyncCheckProjectPermissions( + const { can: canUpdateSubscription } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx index ae9369046dc0a..ca0a81d48bb02 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx @@ -11,7 +11,7 @@ import AlertError from 'components/ui/AlertError' import NoPermission from 'components/ui/NoPermission' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useOrgSettingsPageStateSnapshot } from 'state/organization-settings' import { Alert, Button } from 'ui' import { Admonition } from 'ui-patterns' @@ -24,8 +24,10 @@ const Subscription = () => { const snap = useOrgSettingsPageStateSnapshot() const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') - const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { isSuccess: isPermissionsLoaded, can: canReadSubscriptions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const { data: subscription, diff --git a/apps/studio/components/interfaces/Organization/Documents/SOC2.tsx b/apps/studio/components/interfaces/Organization/Documents/SOC2.tsx index f08ed28a53e13..86de90ef1f051 100644 --- a/apps/studio/components/interfaces/Organization/Documents/SOC2.tsx +++ b/apps/studio/components/interfaces/Organization/Documents/SOC2.tsx @@ -12,7 +12,7 @@ import { import NoPermission from 'components/ui/NoPermission' import { getDocument } from 'data/documents/document-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' @@ -23,8 +23,10 @@ export const SOC2 = () => { const slug = organization?.slug const { mutate: sendEvent } = useSendEventMutation() - const { can: canReadSubscriptions, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { can: canReadSubscriptions, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const currentPlan = organization?.plan diff --git a/apps/studio/components/interfaces/Organization/Documents/SecurityQuestionnaire.tsx b/apps/studio/components/interfaces/Organization/Documents/SecurityQuestionnaire.tsx index 2e165448d95f8..e91c8735f4c4f 100644 --- a/apps/studio/components/interfaces/Organization/Documents/SecurityQuestionnaire.tsx +++ b/apps/studio/components/interfaces/Organization/Documents/SecurityQuestionnaire.tsx @@ -11,7 +11,7 @@ import { import NoPermission from 'components/ui/NoPermission' import { getDocument } from 'data/documents/document-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button } from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' @@ -21,8 +21,10 @@ export const SecurityQuestionnaire = () => { const slug = organization?.slug const { mutate: sendEvent } = useSendEventMutation() - const { can: canReadSubscriptions, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { can: canReadSubscriptions, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const currentPlan = organization?.plan diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/DataPrivacyForm.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/DataPrivacyForm.tsx index b90faee201141..fad4e87624bd7 100644 --- a/apps/studio/components/interfaces/Organization/GeneralSettings/DataPrivacyForm.tsx +++ b/apps/studio/components/interfaces/Organization/GeneralSettings/DataPrivacyForm.tsx @@ -3,13 +3,13 @@ import { useEffect } from 'react' import { FormActions } from 'components/ui/Forms/FormActions' import { useAIOptInForm } from 'hooks/forms/useAIOptInForm' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Card, CardContent, CardFooter, Form_Shadcn_ } from 'ui' import { AIOptInLevelSelector } from './AIOptInLevelSelector' export const DataPrivacyForm = () => { const { form, onSubmit, isUpdating, currentOptInLevel } = useAIOptInForm() - const { can: canUpdateOrganization } = useAsyncCheckProjectPermissions( + const { can: canUpdateOrganization } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/DeleteOrganizationButton.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/DeleteOrganizationButton.tsx index 5fd8de703b868..238f25c5829af 100644 --- a/apps/studio/components/interfaces/Organization/GeneralSettings/DeleteOrganizationButton.tsx +++ b/apps/studio/components/interfaces/Organization/GeneralSettings/DeleteOrganizationButton.tsx @@ -6,7 +6,7 @@ import { toast } from 'sonner' import { LOCAL_STORAGE_KEYS } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useOrganizationDeleteMutation } from 'data/organizations/organization-delete-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button, Form, Input, Modal } from 'ui' @@ -24,7 +24,7 @@ export const DeleteOrganizationButton = () => { '' ) - const { can: canDeleteOrganization } = useAsyncCheckProjectPermissions( + const { can: canDeleteOrganization } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDetailsForm.tsx b/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDetailsForm.tsx index 797f791e92ec1..95f3ebaadda23 100644 --- a/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDetailsForm.tsx +++ b/apps/studio/components/interfaces/Organization/GeneralSettings/OrganizationDetailsForm.tsx @@ -11,7 +11,7 @@ import CopyButton from 'components/ui/CopyButton' import { FormActions } from 'components/ui/Forms/FormActions' import { useOrganizationUpdateMutation } from 'data/organizations/organization-update-mutation' import { invalidateOrganizationsQuery } from 'data/organizations/organizations-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import type { ResponseError } from 'types' import { @@ -35,7 +35,7 @@ export const OrganizationDetailsForm = () => { const queryClient = useQueryClient() const { data: selectedOrganization } = useSelectedOrganizationQuery() - const { can: canUpdateOrganization } = useAsyncCheckProjectPermissions( + const { can: canUpdateOrganization } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx b/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx index 453b0ed4f3102..5ccf2a0111a29 100644 --- a/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx +++ b/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx @@ -19,7 +19,7 @@ import { useGitHubAuthorizationQuery } from 'data/integrations/github-authorizat import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-connection-delete-mutation' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' import type { IntegrationProjectConnection } from 'data/integrations/integrations.types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { BASE_PATH } from 'lib/constants' @@ -50,12 +50,12 @@ const IntegrationSettings = () => { const showVercelIntegration = useIsFeatureEnabled('integrations:vercel') const { can: canReadGithubConnection, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'integrations.github_connections') - const { can: canCreateGitHubConnection } = useAsyncCheckProjectPermissions( + useAsyncCheckPermissions(PermissionAction.READ, 'integrations.github_connections') + const { can: canCreateGitHubConnection } = useAsyncCheckPermissions( PermissionAction.CREATE, 'integrations.github_connections' ) - const { can: canUpdateGitHubConnection } = useAsyncCheckProjectPermissions( + const { can: canUpdateGitHubConnection } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'integrations.github_connections' ) diff --git a/apps/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSection.tsx b/apps/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSection.tsx index 673df43cff6c4..eea5037c28399 100644 --- a/apps/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSection.tsx +++ b/apps/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSection.tsx @@ -5,11 +5,11 @@ import { ScaffoldSectionDetail, } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import InvoicesSettings from './InvoicesSettings' const InvoicesSection = () => { - const { isSuccess: isPermissionsLoaded, can: canReadInvoices } = useAsyncCheckProjectPermissions( + const { isSuccess: isPermissionsLoaded, can: canReadInvoices } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx index 08a9259f0636a..83d34da16d604 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthAppRow.tsx @@ -4,7 +4,7 @@ import { Edit, MoreVertical, Trash } from 'lucide-react' import Table from 'components/to-be-cleaned/Table' import CopyButton from 'components/ui/CopyButton' import type { OAuthApp } from 'data/oauth/oauth-apps-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, DropdownMenu, @@ -25,11 +25,11 @@ export interface OAuthAppRowProps { } export const OAuthAppRow = ({ app, onSelectEdit, onSelectDelete }: OAuthAppRowProps) => { - const { can: canUpdateOAuthApps } = useAsyncCheckProjectPermissions( + const { can: canUpdateOAuthApps } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'approved_oauth_apps' ) - const { can: canDeleteOAuthApps } = useAsyncCheckProjectPermissions( + const { can: canDeleteOAuthApps } = useAsyncCheckPermissions( PermissionAction.DELETE, 'approved_oauth_apps' ) diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx index ddeff5064ec9c..8d303c8c2f9ae 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx @@ -13,7 +13,7 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { AuthorizedApp, useAuthorizedAppsQuery } from 'data/oauth/authorized-apps-query' import { OAuthAppCreateResponse } from 'data/oauth/oauth-app-create-mutation' import { OAuthApp, useOAuthAppsQuery } from 'data/oauth/oauth-apps-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, cn } from 'ui' import { AuthorizedAppRow } from './AuthorizedAppRow' import { DeleteAppModal } from './DeleteAppModal' @@ -34,9 +34,11 @@ export const OAuthApps = () => { const [selectedAppToDelete, setSelectedAppToDelete] = useState() const [selectedAppToRevoke, setSelectedAppToRevoke] = useState() - const { can: canReadOAuthApps, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'approved_oauth_apps') - const { can: canCreateOAuthApps } = useAsyncCheckProjectPermissions( + const { can: canReadOAuthApps, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.READ, + 'approved_oauth_apps' + ) + const { can: canCreateOAuthApps } = useAsyncCheckPermissions( PermissionAction.CREATE, 'approved_oauth_apps' ) diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/OAuthSecrets.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/OAuthSecrets.tsx index 84e003a9e8358..5cc983beeb9b5 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/OAuthSecrets.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/OAuthSecrets.tsx @@ -7,7 +7,7 @@ import { InlineLink } from 'components/ui/InlineLink' import { useClientSecretCreateMutation } from 'data/oauth-secrets/client-secret-create-mutation' import { CreatedSecret, useClientSecretsQuery } from 'data/oauth-secrets/client-secrets-query' import { OAuthApp } from 'data/oauth/oauth-apps-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Alert_Shadcn_, AlertTitle_Shadcn_, InfoIcon } from 'ui' import { SecretRow } from './SecretRow' @@ -18,10 +18,7 @@ interface Props { export const OAuthSecrets = ({ selectedApp }: Props) => { const { slug } = useParams() const [createdSecret, setCreatedSecret] = useState() - const { can: canManageSecrets } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'oauth_apps' - ) + const { can: canManageSecrets } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'oauth_apps') const { id: appId } = selectedApp ?? {} diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/SecretRow.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/SecretRow.tsx index 92e3e3a55aa73..1b080b230270e 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/SecretRow.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthSecrets/SecretRow.tsx @@ -10,7 +10,7 @@ import CopyButton from 'components/ui/CopyButton' import { useClientSecretDeleteMutation } from 'data/oauth-secrets/client-secret-delete-mutation' import { Secret, useClientSecretsQuery } from 'data/oauth-secrets/client-secrets-query' import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { cn } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' @@ -22,10 +22,7 @@ export interface SecretRowProps { export const SecretRow = ({ secret, appId }: SecretRowProps) => { const { slug } = useParams() const [showDeleteModal, setShowDeleteModal] = useState(false) - const { can: canManageSecrets } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'oauth_apps' - ) + const { can: canManageSecrets } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'oauth_apps') const { data } = useClientSecretsQuery({ slug, appId }) const secrets = data?.client_secrets ?? [] diff --git a/apps/studio/components/interfaces/Organization/SecuritySettings/SecuritySettings.tsx b/apps/studio/components/interfaces/Organization/SecuritySettings/SecuritySettings.tsx index 1dd91caccb024..4254c054a91d9 100644 --- a/apps/studio/components/interfaces/Organization/SecuritySettings/SecuritySettings.tsx +++ b/apps/studio/components/interfaces/Organization/SecuritySettings/SecuritySettings.tsx @@ -16,7 +16,7 @@ import { useOrganizationMembersQuery } from 'data/organizations/organization-mem import { useOrganizationMfaToggleMutation } from 'data/organizations/organization-mfa-mutation' import { useOrganizationMfaQuery } from 'data/organizations/organization-mfa-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProfile } from 'lib/profile' import { @@ -48,9 +48,11 @@ export const SecuritySettings = () => { const { data: selectedOrganization } = useSelectedOrganizationQuery() const { data: members } = useOrganizationMembersQuery({ slug }) - const { can: canReadMfaConfig, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'organizations') - const { can: canUpdateMfaConfig } = useAsyncCheckProjectPermissions( + const { can: canReadMfaConfig, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.READ, + 'organizations' + ) + const { can: canUpdateMfaConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index 24b1a8a9fed71..2a2cd9f52dcd1 100644 --- a/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/apps/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -16,7 +16,7 @@ import { } from 'data/organizations/organization-members-query' import { usePermissionsQuery } from 'data/permissions/permissions-query' import { useProjectsQuery } from 'data/projects/projects-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProfile } from 'lib/profile' @@ -69,14 +69,14 @@ export const MemberActions = ({ member }: MemberActionsProps) => { const roleId = member.role_ids?.[0] ?? -1 const canRemoveMember = member.role_ids.every((id) => rolesRemovable.includes(id)) - const { can: canCreateUserInvites } = useAsyncCheckProjectPermissions( + const { can: canCreateUserInvites } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_invites', { resource: { role_id: roleId } } ) const canResendInvite = canCreateUserInvites && hasOrgRole - const { can: canDeleteUserInvites } = useAsyncCheckProjectPermissions( + const { can: canDeleteUserInvites } = useAsyncCheckPermissions( PermissionAction.DELETE, 'user_invites', { resource: { role_id: roleId } } diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index d26883fc6706f..a5b718bc3ed40 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -11,7 +11,7 @@ import NoPermission from 'components/ui/NoPermission' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectsQuery } from 'data/projects/projects-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants/metrics' import { @@ -43,8 +43,10 @@ export const Usage = () => { const selectedProjectRef = selectedProjectRefInputValue === 'all-projects' ? undefined : selectedProjectRefInputValue - const { can: canReadSubscriptions, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.subscriptions') + const { can: canReadSubscriptions, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.BILLING_READ, + 'stripe.subscriptions' + ) const { data: organization } = useSelectedOrganizationQuery() const { data, isSuccess } = useProjectsQuery() diff --git a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx index 08f008921b18e..10d9573184dd1 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/Header.tsx @@ -6,7 +6,7 @@ import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { getTemporaryAPIKey } from 'data/api-keys/temp-api-keys-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { ChooseChannelPopover } from './ChooseChannelPopover' import { RealtimeFilterPopover } from './RealtimeFilterPopover' @@ -23,7 +23,7 @@ export const Header = ({ config, onChangeConfig }: HeaderProps) => { const { ref } = useParams() const { data: org } = useSelectedOrganizationQuery() - const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' ) diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx index 8f5a24e2d41ea..7fc0eb6271e02 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessagesTable.tsx @@ -10,7 +10,7 @@ import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' import ShimmerLine from 'components/ui/ShimmerLine' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button, IconBroadcast, IconDatabaseChanges, IconPresence, cn } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' @@ -34,7 +34,7 @@ const NoResultAlert = ({ }) => { const { ref } = useParams() - const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' ) diff --git a/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx b/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx index e9caa8b52e014..c755fb5118572 100644 --- a/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx +++ b/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx @@ -17,7 +17,7 @@ import { REALTIME_DEFAULT_CONFIG, useRealtimeConfigurationQuery, } from 'data/realtime/realtime-config-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { @@ -41,7 +41,7 @@ export const RealtimeSettings = () => { const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() const { data: organization } = useSelectedOrganizationQuery() - const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.REALTIME_ADMIN_READ, '*' ) diff --git a/apps/studio/components/interfaces/Reports/Reports.tsx b/apps/studio/components/interfaces/Reports/Reports.tsx index f7f1f24c37eb3..aa8d37a780e57 100644 --- a/apps/studio/components/interfaces/Reports/Reports.tsx +++ b/apps/studio/components/interfaces/Reports/Reports.tsx @@ -22,7 +22,7 @@ import { useContentUpsertMutation, } from 'data/content/content-upsert-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Metric, TIME_PERIODS_REPORTS } from 'lib/constants/metrics' @@ -82,7 +82,7 @@ const Reports = () => { const currentReport = userContents?.content.find((report) => report.id === id) const currentReportContent = currentReport?.content as Dashboards.Content - const { can: canReadReport, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions( + const { can: canReadReport, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.READ, 'user_content', { @@ -94,7 +94,7 @@ const Reports = () => { subject: { id: profile?.id }, } ) - const { can: canUpdateReport } = useAsyncCheckProjectPermissions( + const { can: canUpdateReport } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'user_content', { diff --git a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx index eae83009246e9..5e9141c41f03d 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx @@ -7,7 +7,7 @@ import { useParams } from 'common' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries' import { ActionCard } from 'components/layouts/Tabs/ActionCard' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { uuidv4 } from 'lib/helpers' @@ -26,7 +26,7 @@ const SQLQuickstarts = () => { const snapV2 = useSqlEditorV2StateSnapshot() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index 40603c824f578..e33799346e0b6 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -7,7 +7,7 @@ import { useParams } from 'common' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries' import { ActionCard } from 'components/layouts/Tabs/ActionCard' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { uuidv4 } from 'lib/helpers' @@ -26,7 +26,7 @@ const SQLTemplates = () => { const snapV2 = useSqlEditorV2StateSnapshot() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/interfaces/SQLEditor/hooks.ts b/apps/studio/components/interfaces/SQLEditor/hooks.ts index 8bba5cf16b466..6d712056d3487 100644 --- a/apps/studio/components/interfaces/SQLEditor/hooks.ts +++ b/apps/studio/components/interfaces/SQLEditor/hooks.ts @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' @@ -24,7 +24,7 @@ export const useNewQuery = () => { const { data: project } = useSelectedProjectQuery() const snapV2 = useSqlEditorV2StateSnapshot() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx b/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx index d5676bced4958..5f2e97cc07e4f 100644 --- a/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx +++ b/apps/studio/components/interfaces/Settings/API/PostgrestConfig.tsx @@ -15,7 +15,7 @@ import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-co import { useProjectPostgrestConfigUpdateMutation } from 'data/config/project-postgrest-config-update-mutation' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { useSchemasQuery } from 'data/database/schemas-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { AlertDescription_Shadcn_, @@ -102,7 +102,7 @@ export const PostgrestConfig = () => { const formId = 'project-postgres-config' const hiddenSchema = ['auth', 'pgbouncer', 'hooks', 'extensions'] const { can: canUpdatePostgrestConfig, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.UPDATE, 'custom_config_postgrest') + useAsyncCheckPermissions(PermissionAction.UPDATE, 'custom_config_postgrest') const isGraphqlExtensionEnabled = (extensions ?? []).find((ext) => ext.name === 'pg_graphql')?.installed_version !== null diff --git a/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx index 3f79a908bf7ec..7e035c727bc5c 100644 --- a/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/CustomDomainSidePanel.tsx @@ -9,7 +9,7 @@ import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon- import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import type { AddonVariantId } from 'data/subscriptions/types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { formatCurrency } from 'lib/helpers' import { useAddonsPagePanel } from 'state/addons-page' @@ -31,7 +31,7 @@ const CustomDomainSidePanel = () => { const [selectedOption, setSelectedOption] = useState('cd_none') - const { can: canUpdateCustomDomain } = useAsyncCheckProjectPermissions( + const { can: canUpdateCustomDomain } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx index 81d1a1dbb798f..01e54ff3308d8 100644 --- a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx @@ -10,7 +10,7 @@ import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon- import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import type { AddonVariantId } from 'data/subscriptions/types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useIsAwsCloudProvider } from 'hooks/misc/useSelectedProject' import { formatCurrency } from 'lib/helpers' @@ -25,7 +25,7 @@ const IPv4SidePanel = () => { const [selectedOption, setSelectedOption] = useState('ipv4_none') - const { can: canUpdateIPv4 } = useAsyncCheckProjectPermissions( + const { can: canUpdateIPv4 } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx index 8e80cf20d8706..cfd5deb17d69e 100644 --- a/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx @@ -13,7 +13,7 @@ import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon- import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import type { AddonVariantId } from 'data/subscriptions/types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { BASE_PATH } from 'lib/constants' @@ -61,7 +61,7 @@ const PITRSidePanel = () => { const [selectedCategory, setSelectedCategory] = useState<'on' | 'off'>('off') const [selectedOption, setSelectedOption] = useState('pitr_0') - const { can: canUpdatePitr } = useAsyncCheckProjectPermissions( + const { can: canUpdatePitr } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/interfaces/Settings/Database/BannedIPs.tsx b/apps/studio/components/interfaces/Settings/Database/BannedIPs.tsx index 29ec00bb24625..952288603b5cd 100644 --- a/apps/studio/components/interfaces/Settings/Database/BannedIPs.tsx +++ b/apps/studio/components/interfaces/Settings/Database/BannedIPs.tsx @@ -12,7 +12,7 @@ import { FormPanel } from 'components/ui/Forms/FormPanel' import { useBannedIPsDeleteMutation } from 'data/banned-ips/banned-ips-delete-mutations' import { useBannedIPsQuery } from 'data/banned-ips/banned-ips-query' import { useUserIPAddressQuery } from 'data/misc/user-ip-address-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Badge, Skeleton } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' @@ -39,15 +39,11 @@ const BannedIPs = () => { const [showUnban, setShowUnban] = useState(false) const [confirmingIP, setConfirmingIP] = useState(null) // Track the IP being confirmed for unban - const { can: canUnbanNetworks } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'projects', - { - resource: { - project_id: project?.id, - }, - } - ) + const { can: canUnbanNetworks } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { + resource: { + project_id: project?.id, + }, + }) const { mutate: unbanIPs, isLoading: isUnbanning } = useBannedIPsDeleteMutation({ onSuccess: () => { diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx index 66127cfa7c921..47e7700820fd5 100644 --- a/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx +++ b/apps/studio/components/interfaces/Settings/Database/ConnectionPooling/ConnectionPooling.tsx @@ -17,7 +17,7 @@ import { useMaxConnectionsQuery } from 'data/database/max-connections-query' import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query' import { usePgbouncerConfigurationUpdateMutation } from 'data/database/pgbouncer-config-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { @@ -51,7 +51,7 @@ export const ConnectionPooling = () => { const { data: project } = useSelectedProjectQuery() const { data: org } = useSelectedOrganizationQuery() - const { can: canUpdateConnectionPoolingConfiguration } = useAsyncCheckProjectPermissions( + const { can: canUpdateConnectionPoolingConfiguration } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { resource: { project_id: project?.id } } diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx index b39ac8008fab8..17e30ace69036 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword.tsx @@ -9,7 +9,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import Panel from 'components/ui/Panel' import PasswordStrengthBar from 'components/ui/PasswordStrengthBar' import { useDatabasePasswordResetMutation } from 'data/database/database-password-reset-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { DEFAULT_MINIMUM_PASSWORD_STRENGTH } from 'lib/constants' import passwordStrength from 'lib/password-strength' @@ -21,7 +21,7 @@ const ResetDbPassword = ({ disabled = false }) => { const isProjectActive = useIsProjectActive() const { data: project } = useSelectedProjectQuery() - const { can: canResetDbPassword } = useAsyncCheckProjectPermissions( + const { can: canResetDbPassword } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Settings/Database/DiskSizeConfiguration.tsx b/apps/studio/components/interfaces/Settings/Database/DiskSizeConfiguration.tsx index f8ae07220fe29..140b110d380e5 100644 --- a/apps/studio/components/interfaces/Settings/Database/DiskSizeConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DiskSizeConfiguration.tsx @@ -12,7 +12,7 @@ import { FormHeader } from 'components/ui/Forms/FormHeader' import Panel from 'components/ui/Panel' import { useProjectDiskResizeMutation } from 'data/config/project-disk-resize-mutation' import { useDatabaseSizeQuery } from 'data/database/database-size-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useUrlState } from 'hooks/ui/useUrlState' @@ -35,7 +35,7 @@ const DiskSizeConfiguration = ({ disabled = false }: DiskSizeConfigurationProps) setUrlParams({ show_increase_disk_size_modal: show ? 'true' : undefined }) } - const { can: canUpdateDiskSizeConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateDiskSizeConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx b/apps/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx index 4418578341e17..65455dd2ef49f 100644 --- a/apps/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx +++ b/apps/studio/components/interfaces/Settings/Database/NetworkRestrictions/NetworkRestrictions.tsx @@ -10,7 +10,7 @@ import { FormPanel } from 'components/ui/Forms/FormPanel' import Panel from 'components/ui/Panel' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useNetworkRestrictionsQuery } from 'data/network-restrictions/network-restrictions-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Badge, @@ -75,7 +75,7 @@ const NetworkRestrictions = () => { const [selectedRestrictionToRemove, setSelectedRestrictionToRemove] = useState() const { data, isLoading } = useNetworkRestrictionsQuery({ projectRef: ref }) - const { can: canUpdateNetworkRestrictions } = useAsyncCheckProjectPermissions( + const { can: canUpdateNetworkRestrictions } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx b/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx index 70d151e18ca7d..70092a729d447 100644 --- a/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Database/SSLConfiguration.tsx @@ -15,7 +15,7 @@ import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query import { useSSLEnforcementQuery } from 'data/ssl-enforcement/ssl-enforcement-query' import { useSSLEnforcementUpdateMutation } from 'data/ssl-enforcement/ssl-enforcement-update-mutation' import { useCustomContent } from 'hooks/custom-content/useCustomContent' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Alert, Button, Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui' @@ -44,7 +44,7 @@ const SSLConfiguration = () => { } ) - const { can: canUpdateSSLEnforcement } = useAsyncCheckProjectPermissions( + const { can: canUpdateSSLEnforcement } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx b/apps/studio/components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx index 0f33a4c6e124c..d59a176634c40 100644 --- a/apps/studio/components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx +++ b/apps/studio/components/interfaces/Settings/General/ComplianceConfig/ProjectComplianceMode.tsx @@ -12,7 +12,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui import { InlineLink } from 'components/ui/InlineLink' import { useComplianceConfigUpdateMutation } from 'data/config/project-compliance-config-mutation' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Switch, Tooltip, TooltipContent, TooltipTrigger } from 'ui' @@ -21,7 +21,7 @@ const ComplianceConfig = () => { const { data: project } = useSelectedProjectQuery() const [isSensitive, setIsSensitive] = useState(false) - const { can: canUpdateComplianceConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateComplianceConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx b/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx index 1f7f77233c6d9..de8a2d0541b41 100644 --- a/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx +++ b/apps/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsConfigureHostname.tsx @@ -9,7 +9,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCheckCNAMERecordMutation } from 'data/custom-domains/check-cname-mutation' import { useCustomDomainCreateMutation } from 'data/custom-domains/custom-domains-create-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Form, Input } from 'ui' @@ -27,7 +27,7 @@ const CustomDomainsConfigureHostname = () => { const FORM_ID = 'custom-domains-form' const endpoint = settings?.app_config?.endpoint - const { can: canConfigureCustomDomain } = useAsyncCheckProjectPermissions( + const { can: canConfigureCustomDomain } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx index a47ecbb6cd6a2..d753a214cec29 100644 --- a/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectButton.tsx @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useState } from 'react' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { DeleteProjectModal } from './DeleteProjectModal' @@ -14,13 +14,9 @@ const DeleteProjectButton = ({ type = 'danger' }: DeleteProjectButtonProps) => { const { data: project } = useSelectedProjectQuery() const [isOpen, setIsOpen] = useState(false) - const { can: canDeleteProject } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'projects', - { - resource: { project_id: project?.id }, - } - ) + const { can: canDeleteProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { + resource: { project_id: project?.id }, + }) return ( <> diff --git a/apps/studio/components/interfaces/Settings/General/General.tsx b/apps/studio/components/interfaces/Settings/General/General.tsx index ea95391fcafe7..b58a5ff716211 100644 --- a/apps/studio/components/interfaces/Settings/General/General.tsx +++ b/apps/studio/components/interfaces/Settings/General/General.tsx @@ -9,7 +9,7 @@ import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui import Panel from 'components/ui/Panel' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useProjectUpdateMutation } from 'data/projects/project-update-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProjectByRefQuery, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { @@ -33,15 +33,11 @@ const General = () => { const formId = 'project-general-settings' const initialValues = { name: project?.name ?? '', ref: project?.ref ?? '' } - const { can: canUpdateProject } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'projects', - { - resource: { - project_id: project?.id, - }, - } - ) + const { can: canUpdateProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { + resource: { + project_id: project?.id, + }, + }) const { mutate: updateProject, isLoading: isUpdating } = useProjectUpdateMutation() diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 1cd34e163ec7b..3f8223e8a6ccf 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -9,7 +9,7 @@ import { useIsProjectActive } from 'components/layouts/ProjectLayout/ProjectCont import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useProjectPauseMutation } from 'data/projects/project-pause-mutation' import { setProjectStatus } from 'data/projects/projects-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useIsAwsK8sCloudProvider, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' @@ -25,7 +25,7 @@ const PauseProjectButton = () => { const projectRef = project?.ref ?? '' const isPaused = project?.status === PROJECT_STATUS.INACTIVE - const { can: canPauseProject } = useAsyncCheckProjectPermissions( + const { can: canPauseProject } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_jobs.projects.pause' ) diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx index 8138ea7b1c0a0..55c0f44e7f327 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/RestartServerButton.tsx @@ -11,7 +11,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useProjectRestartMutation } from 'data/projects/project-restart-mutation' import { useProjectRestartServicesMutation } from 'data/projects/project-restart-services-mutation' import { setProjectStatus } from 'data/projects/projects-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsAwsK8sCloudProvider, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -35,7 +35,7 @@ const RestartServerButton = () => { const projectRegion = project?.region ?? '' const projectRestartDisabled = useFlag('disableProjectRestarts') - const { can: canRestartProject } = useAsyncCheckProjectPermissions( + const { can: canRestartProject } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'reboot' ) diff --git a/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx index 747500510b5bf..68d3413aab03a 100644 --- a/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx @@ -9,7 +9,7 @@ import { DocsButton } from 'components/ui/DocsButton' import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useProjectTransferMutation } from 'data/projects/project-transfer-mutation' import { useProjectTransferPreviewQuery } from 'data/projects/project-transfer-preview-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, InfoIcon, Listbox, Loading, Modal, WarningIcon } from 'ui' import { Admonition } from 'ui-patterns' @@ -58,7 +58,7 @@ const TransferProjectButton = () => { } }, [isOpen]) - const { can: canTransferProject } = useAsyncCheckProjectPermissions( + const { can: canTransferProject } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx index f74301cc8c6ab..8d6fb5b6211ca 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.tsx @@ -16,7 +16,7 @@ import { ReplicaInitializationStatus, useReadReplicasStatusesQuery, } from 'data/read-replicas/replicas-status-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { timeout } from 'lib/helpers' import { type AWS_REGIONS_KEYS } from 'shared-data' @@ -60,10 +60,7 @@ const InstanceConfigurationUI = ({ diagramOnly = false }: InstanceConfigurationU const [selectedReplicaToDrop, setSelectedReplicaToDrop] = useState() const [selectedReplicaToRestart, setSelectedReplicaToRestart] = useState() - const { can: canManageReplicas } = useAsyncCheckProjectPermissions( - PermissionAction.CREATE, - 'projects' - ) + const { can: canManageReplicas } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') const { data: loadBalancers, diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index a14c87464c1fc..07dc3bf06743c 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -14,7 +14,7 @@ import { useReadReplicasStatusesQuery, } from 'data/read-replicas/replicas-status-query' import { formatDatabaseID } from 'data/read-replicas/replicas.utils' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' @@ -191,10 +191,7 @@ export const ReplicaNode = ({ data }: NodeProps) => { } = data const { ref } = useParams() const dbSelectorState = useDatabaseSelectorStateSnapshot() - const { can: canManageReplicas } = useAsyncCheckProjectPermissions( - PermissionAction.CREATE, - 'projects' - ) + const { can: canManageReplicas } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ 'project_homepage:show_instance_size', ]) diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx index 6140367095b9a..d933d78905e1d 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx @@ -19,7 +19,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { formatDatabaseID } from 'data/read-replicas/replicas.utils' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import type { AWS_REGIONS_KEYS } from 'shared-data' @@ -64,10 +64,7 @@ const MapView = ({ y: number region: { key: string; country?: string; name?: string; region?: string } }>() - const { can: canManageReplicas } = useAsyncCheckProjectPermissions( - PermissionAction.CREATE, - 'projects' - ) + const { can: canManageReplicas } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) const { data } = useReadReplicasQuery({ projectRef: ref }) diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx index e9c083c197542..e97061a5869a8 100644 --- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx +++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx @@ -16,7 +16,7 @@ import { useGitHubConnectionDeleteMutation } from 'data/integrations/github-conn import { useGitHubConnectionUpdateMutation } from 'data/integrations/github-connection-update-mutation' import { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query' import type { GitHubConnection } from 'data/integrations/integrations.types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { openInstallGitHubIntegrationWindow } from 'lib/github' @@ -72,11 +72,11 @@ const GitHubIntegrationConnectionForm = ({ const [repoComboBoxOpen, setRepoComboboxOpen] = useState(false) const isParentProject = !Boolean(selectedProject?.parent_project_ref) - const { can: canUpdateGitHubConnection } = useAsyncCheckProjectPermissions( + const { can: canUpdateGitHubConnection } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'integrations.github_connections' ) - const { can: canCreateGitHubConnection } = useAsyncCheckProjectPermissions( + const { can: canCreateGitHubConnection } = useAsyncCheckPermissions( PermissionAction.CREATE, 'integrations.github_connections' ) diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx index 36256f769ef90..2d538b3a805eb 100644 --- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx +++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx @@ -11,7 +11,7 @@ import { import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { BASE_PATH, IS_PLATFORM } from 'lib/constants' import { cn } from 'ui' @@ -33,7 +33,7 @@ const GitHubSection = () => { const { data: organization } = useSelectedOrganizationQuery() const { can: canReadGitHubConnection, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'integrations.github_connections') + useAsyncCheckPermissions(PermissionAction.READ, 'integrations.github_connections') const isProPlanAndUp = organization?.plan?.id !== 'free' const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp diff --git a/apps/studio/components/interfaces/Settings/Integrations/VercelIntegration/VercelSection.tsx b/apps/studio/components/interfaces/Settings/Integrations/VercelIntegration/VercelSection.tsx index 74c721dbe2ffe..59c6ca8d8033d 100644 --- a/apps/studio/components/interfaces/Settings/Integrations/VercelIntegration/VercelSection.tsx +++ b/apps/studio/components/interfaces/Settings/Integrations/VercelIntegration/VercelSection.tsx @@ -26,7 +26,7 @@ import type { IntegrationName, IntegrationProjectConnection, } from 'data/integrations/integrations.types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { pluralize } from 'lib/helpers' @@ -44,12 +44,12 @@ const VercelSection = ({ isProjectScoped }: { isProjectScoped: boolean }) => { const isBranch = project?.parent_project_ref !== undefined const { can: canReadVercelConnection, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'integrations.vercel_connections') - const { can: canCreateVercelConnection } = useAsyncCheckProjectPermissions( + useAsyncCheckPermissions(PermissionAction.READ, 'integrations.vercel_connections') + const { can: canCreateVercelConnection } = useAsyncCheckPermissions( PermissionAction.CREATE, 'integrations.vercel_connections' ) - const { can: canUpdateVercelConnection } = useAsyncCheckProjectPermissions( + const { can: canUpdateVercelConnection } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'integrations.vercel_connections' ) diff --git a/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx b/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx index b6e32e5d1c393..0e439d9cbd82d 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogTable.tsx @@ -10,7 +10,7 @@ import { IS_PLATFORM, useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DownloadResultsButton } from 'components/ui/DownloadResultsButton' import { useSelectedLog } from 'hooks/analytics/useSelectedLog' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useProfile } from 'lib/profile' import { toast } from 'sonner' import { ResponseError } from 'types' @@ -94,7 +94,7 @@ const LogTable = ({ const [selectionOpen, setSelectionOpen] = useState(false) const [selectedRow, setSelectedRow] = useState(null) - const { can: canCreateLogQuery } = useAsyncCheckProjectPermissions( + const { can: canCreateLogQuery } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/interfaces/Storage/BucketRow.tsx b/apps/studio/components/interfaces/Storage/BucketRow.tsx index 294939db04123..031de7b14125a 100644 --- a/apps/studio/components/interfaces/Storage/BucketRow.tsx +++ b/apps/studio/components/interfaces/Storage/BucketRow.tsx @@ -7,7 +7,7 @@ import { DeleteBucketModal } from 'components/interfaces/Storage/DeleteBucketMod import { EditBucketModal } from 'components/interfaces/Storage/EditBucketModal' import { EmptyBucketModal } from 'components/interfaces/Storage/EmptyBucketModal' import type { Bucket } from 'data/storage/buckets-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Badge, Button, @@ -29,10 +29,7 @@ export interface BucketRowProps { } export const BucketRow = ({ bucket, projectRef = '', isSelected = false }: BucketRowProps) => { - const { can: canUpdateBuckets } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateBuckets } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') const [modal, setModal] = useState(null) const onClose = () => setModal(null) diff --git a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx index a6ad858197a24..1eddfd8128d97 100644 --- a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx @@ -17,7 +17,7 @@ import { useProjectStorageConfigQuery } from 'data/config/project-storage-config import { useBucketCreateMutation } from 'data/storage/bucket-create-mutation' import { useIcebergWrapperCreateMutation } from 'data/storage/iceberg-wrapper-create-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { BASE_PATH, IS_PLATFORM } from 'lib/constants' import { @@ -103,10 +103,7 @@ export const CreateBucketModal = () => { const [visible, setVisible] = useState(false) const [selectedUnit, setSelectedUnit] = useState(StorageSizeUnits.MB) - const { can: canCreateBuckets } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canCreateBuckets } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') const { mutate: sendEvent } = useSendEventMutation() const { mutateAsync: createBucket, isLoading: isCreating } = useBucketCreateMutation({ diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/ColumnContextMenu.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/ColumnContextMenu.tsx index d0acf14b0337f..746d1e21f0990 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/ColumnContextMenu.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/ColumnContextMenu.tsx @@ -3,7 +3,7 @@ import { Item, Menu, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { ChevronRight, ChevronsDown, ChevronsUp, Clipboard, Eye, FolderPlus } from 'lucide-react' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import { @@ -28,10 +28,7 @@ export const ColumnContextMenu = ({ id = '' }: ColumnContextMenuProps) => { addNewFolderPlaceholder, } = useStorageExplorerStateSnapshot() - const { can: canUpdateFiles } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') const onSelectCreateFolder = (columnIndex = -1) => { addNewFolderPlaceholder(columnIndex) diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx index e65e425cdb416..ebbaee5a51695 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerColumn.tsx @@ -8,7 +8,7 @@ import { toast } from 'sonner' import InfiniteList from 'components/ui/InfiniteList' import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' import { formatBytes } from 'lib/helpers' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' @@ -83,10 +83,7 @@ export const FileExplorerColumn = ({ const fileExplorerColumnRef = useRef(null) const snap = useStorageExplorerStateSnapshot() - const { can: canUpdateStorage } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateStorage } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') useEffect(() => { if (fileExplorerColumnRef) { diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx index 0d080f6c90f6b..72135eb819e90 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeader.tsx @@ -19,7 +19,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { APIDocsButton } from 'components/ui/APIDocsButton' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import { Button, @@ -179,10 +179,7 @@ export const FileExplorerHeader = ({ const breadcrumbs = columns.map((column) => column.name) const backDisabled = columns.length <= 1 - const { can: canUpdateStorage } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateStorage } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') useEffect(() => { if (itemSearchString) setSearchString(itemSearchString) diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx index 9b4559c5b5b71..f7b45bd74cc94 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerHeaderSelection.tsx @@ -3,17 +3,14 @@ import { Download, Move, Trash2, X } from 'lucide-react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import { Button } from 'ui' import { downloadFile } from './StorageExplorer.utils' export const FileExplorerHeaderSelection = () => { const { ref: projectRef, bucketId } = useParams() - const { can: canUpdateFiles } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') const { selectedItems, diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerRow.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerRow.tsx index df4a5eaa8ef1d..467af3341c71c 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerRow.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/FileExplorerRow.tsx @@ -19,7 +19,7 @@ import SVG from 'react-inlinesvg' import { useParams } from 'common' import type { ItemRenderer } from 'components/ui/InfiniteList' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' import { formatBytes } from 'lib/helpers' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' @@ -139,10 +139,7 @@ export const FileExplorerRow: ItemRenderer = const isOpened = openedFolders.length > columnIndex ? openedFolders[columnIndex].name === item.name : false const isPreviewed = !isEmpty(selectedFilePreview) && isEqual(selectedFilePreview?.id, item.id) - const { can: canUpdateFiles } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') const onSelectFile = async (columnIndex: number, file: StorageItem) => { popColumnAtIndex(columnIndex) diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/FolderContextMenu.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/FolderContextMenu.tsx index 1242a841a7b62..c91c498c0bbb1 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/FolderContextMenu.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/FolderContextMenu.tsx @@ -3,7 +3,7 @@ import { Clipboard, Download, Edit, Trash2 } from 'lucide-react' import { Item, Menu, Separator } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import { copyPathToFolder } from './StorageExplorer.utils' @@ -14,10 +14,7 @@ interface FolderContextMenuProps { export const FolderContextMenu = ({ id = '' }: FolderContextMenuProps) => { const { openedFolders, downloadFolder, setSelectedItemToRename, setSelectedItemsToDelete } = useStorageExplorerStateSnapshot() - const { can: canUpdateFiles } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') return ( diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/ItemContextMenu.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/ItemContextMenu.tsx index 0712e4b6578c7..359a35878c7f3 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/ItemContextMenu.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/ItemContextMenu.tsx @@ -4,7 +4,7 @@ import { Item, Menu, Separator, Submenu } from 'react-contexify' import 'react-contexify/dist/ReactContexify.css' import { useParams } from 'common' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import { URL_EXPIRY_DURATION } from '../Storage.constants' import { StorageItemWithColumn } from '../Storage.types' @@ -28,10 +28,7 @@ export const ItemContextMenu = ({ id = '' }: ItemContextMenuProps) => { } = useStorageExplorerStateSnapshot() const { onCopyUrl } = useCopyUrl() const isPublic = selectedBucket.public - const { can: canUpdateFiles } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') const onHandleClick = async (event: any, item: StorageItemWithColumn, expiresIn?: number) => { if (item.isCorrupted) return diff --git a/apps/studio/components/interfaces/Storage/StorageExplorer/PreviewPane.tsx b/apps/studio/components/interfaces/Storage/StorageExplorer/PreviewPane.tsx index 44adc6c0b305a..d4cdb53dab5ec 100644 --- a/apps/studio/components/interfaces/Storage/StorageExplorer/PreviewPane.tsx +++ b/apps/studio/components/interfaces/Storage/StorageExplorer/PreviewPane.tsx @@ -6,7 +6,7 @@ import SVG from 'react-inlinesvg' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' import { formatBytes } from 'lib/helpers' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' @@ -127,10 +127,7 @@ export const PreviewPane = () => { } = useStorageExplorerStateSnapshot() const { onCopyUrl } = useCopyUrl() - const { can: canUpdateFiles } = useAsyncCheckProjectPermissions( - PermissionAction.STORAGE_WRITE, - '*' - ) + const { can: canUpdateFiles } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') if (!file) return null diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/CreateCredentialModal.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/CreateCredentialModal.tsx index cb5b6de2ab9b8..0cd7cbf7c6228 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/CreateCredentialModal.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/CreateCredentialModal.tsx @@ -9,7 +9,7 @@ import { useParams } from 'common' import { useIsProjectActive } from 'components/layouts/ProjectLayout/ProjectContext' import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' import { useS3AccessKeyCreateMutation } from 'data/storage/s3-access-key-create-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Dialog, @@ -39,7 +39,7 @@ export const CreateCredentialModal = ({ visible, onOpenChange }: CreateCredentia const isProjectActive = useIsProjectActive() const [showSuccess, setShowSuccess] = useState(false) - const { can: canCreateCredentials } = useAsyncCheckProjectPermissions( + const { can: canCreateCredentials } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/S3Connection.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/S3Connection.tsx index 0057b4e751d4b..a4301b64d4075 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/S3Connection.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/S3Connection.tsx @@ -22,7 +22,7 @@ import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation' import { useStorageCredentialsQuery } from 'data/storage/s3-access-key-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { AlertDescription_Shadcn_, @@ -54,9 +54,11 @@ export const S3Connection = () => { const [openDeleteDialog, setOpenDeleteDialog] = useState(false) const [deleteCred, setDeleteCred] = useState<{ id: string; description: string }>() - const { can: canReadS3Credentials, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.STORAGE_ADMIN_READ, '*') - const { can: canUpdateStorageSettings } = useAsyncCheckProjectPermissions( + const { can: canReadS3Credentials, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.STORAGE_ADMIN_READ, + '*' + ) + const { can: canUpdateStorageSettings } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageCredItem.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageCredItem.tsx index 7eb0717b6ff70..216449b41685e 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageCredItem.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageCredItem.tsx @@ -3,7 +3,7 @@ import { differenceInDays } from 'date-fns' import { MoreVertical, TrashIcon } from 'lucide-react' import CopyButton from 'components/ui/CopyButton' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, DropdownMenu, @@ -25,7 +25,7 @@ export const StorageCredItem = ({ access_key: string onDeleteClick: (id: string) => void }) => { - const { can: canRemoveAccessKey } = useAsyncCheckProjectPermissions( + const { can: canRemoveAccessKey } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx index 0a046eb0c9a5a..bd0cfb22d806b 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx @@ -16,7 +16,7 @@ import UpgradeToPro from 'components/ui/UpgradeToPro' import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation' import { useBucketsQuery } from 'data/storage/buckets-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { formatBytes } from 'lib/helpers' import { @@ -58,9 +58,11 @@ interface StorageSettingsState { export const StorageSettings = () => { const { ref: projectRef } = useParams() - const { can: canReadStorageSettings, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.STORAGE_ADMIN_READ, '*') - const { can: canUpdateStorageSettings } = useAsyncCheckProjectPermissions( + const { can: canReadStorageSettings, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.STORAGE_ADMIN_READ, + '*' + ) + const { can: canUpdateStorageSettings } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_WRITE, '*' ) diff --git a/apps/studio/components/interfaces/Storage/__tests__/CreateBucketModal.test.tsx b/apps/studio/components/interfaces/Storage/__tests__/CreateBucketModal.test.tsx index 36ed53271691e..e3b18f770da3c 100644 --- a/apps/studio/components/interfaces/Storage/__tests__/CreateBucketModal.test.tsx +++ b/apps/studio/components/interfaces/Storage/__tests__/CreateBucketModal.test.tsx @@ -13,7 +13,7 @@ describe(`CreateBucketModal`, () => { beforeEach(() => { vi.mock(`hooks/misc/useCheckPermissions`, () => ({ useCheckPermissions: vi.fn(), - useAsyncCheckProjectPermissions: vi.fn().mockImplementation(() => ({ can: true })), + useAsyncCheckPermissions: vi.fn().mockImplementation(() => ({ can: true })), })) // useParams routerMock.setCurrentUrl(`/project/default/storage/buckets`) diff --git a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 0322cba739a24..2a11307f97c24 100644 --- a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -22,7 +22,7 @@ import { } from 'data/table-editor/table-editor-types' import { useTableUpdateMutation } from 'data/tables/table-update-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' @@ -115,9 +115,11 @@ export const GridHeaderActions = ({ table, isRefetching }: GridHeaderActionsProp }, }) - const { can: canSqlWriteTables, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') - const { can: canSqlWriteColumns } = useAsyncCheckProjectPermissions( + const { can: canSqlWriteTables, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'tables' + ) + const { can: canSqlWriteColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) diff --git a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 2cceac5f76d64..8294554566da0 100644 --- a/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -14,7 +14,7 @@ import { isView, TableLike, } from 'data/table-editor/table-editor-types' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useDashboardHistory } from 'hooks/misc/useDashboardHistory' import { useUrlState } from 'hooks/ui/useUrlState' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' @@ -48,11 +48,11 @@ export const TableGridEditor = ({ const [{ view: selectedView = 'data' }] = useUrlState() - const { can: canEditTables } = useAsyncCheckProjectPermissions( + const { can: canEditTables } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) - const { can: canEditColumns } = useAsyncCheckProjectPermissions( + const { can: canEditColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) diff --git a/apps/studio/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice.tsx b/apps/studio/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice.tsx index de2a75a3580aa..f101320651af6 100644 --- a/apps/studio/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice.tsx +++ b/apps/studio/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice.tsx @@ -4,7 +4,7 @@ import Link from 'next/link' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useAppStateSnapshot } from 'state/app-state' import { Button } from 'ui' @@ -12,7 +12,7 @@ export const BranchingPITRNotice = () => { const { ref } = useParams() const snap = useAppStateSnapshot() - const { can: canUpdateSubscription } = useAsyncCheckProjectPermissions( + const { can: canUpdateSubscription } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx b/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx index ef94be5b6d26e..76b488fc84587 100644 --- a/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx +++ b/apps/studio/components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout.tsx @@ -15,7 +15,7 @@ import NoPermission from 'components/ui/NoPermission' import { useEdgeFunctionBodyQuery } from 'data/edge-functions/edge-function-body-query' import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { withAuth } from 'hooks/misc/withAuth' import { @@ -43,7 +43,7 @@ const EdgeFunctionDetailsLayout = ({ const { mutate: sendEvent } = useSendEventMutation() const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled() - const { isLoading, can: canReadFunctions } = useAsyncCheckProjectPermissions( + const { isLoading, can: canReadFunctions } = useAsyncCheckPermissions( PermissionAction.FUNCTIONS_READ, '*' ) diff --git a/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx b/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx index 30c4ae8bbf4d9..9965ed65b2a98 100644 --- a/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx +++ b/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx @@ -3,7 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { PropsWithChildren } from 'react' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { withAuth } from 'hooks/misc/withAuth' import ProjectLayout from '../ProjectLayout/ProjectLayout' import { LogsSidebarMenuV2 } from './LogsSidebarMenuV2' @@ -13,7 +13,7 @@ interface LogsLayoutProps { } const LogsLayout = ({ title, children }: PropsWithChildren) => { - const { isLoading, can: canUseLogsExplorer } = useAsyncCheckProjectPermissions( + const { isLoading, can: canUseLogsExplorer } = useAsyncCheckPermissions( PermissionAction.ANALYTICS_READ, 'logflare' ) diff --git a/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx b/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx index d82d07658b797..f0141fa27639b 100644 --- a/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/PauseFailedState.tsx @@ -10,7 +10,7 @@ import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { InlineLink } from 'components/ui/InlineLink' import { useBackupDownloadMutation } from 'data/database/backup-download-mutation' import { useDownloadableBackupQuery } from 'data/database/backup-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, CriticalIcon, DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from 'ui' @@ -19,13 +19,9 @@ export const PauseFailedState = () => { const { data: project } = useSelectedProjectQuery() const [visible, setVisible] = useState(false) - const { can: canDeleteProject } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'projects', - { - resource: { project_id: project?.id }, - } - ) + const { can: canDeleteProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { + resource: { project_id: project?.id }, + }) const { data } = useDownloadableBackupQuery({ projectRef: ref }) const backups = data?.backups ?? [] diff --git a/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx b/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx index 353d948b00b1c..508f1a0b00276 100644 --- a/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/PausedState/ProjectPausedState.tsx @@ -18,7 +18,7 @@ import { PostgresEngine, ReleaseChannel } from 'data/projects/new-project.consta 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 { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { usePHFlag } from 'hooks/ui/useFlag' @@ -94,7 +94,7 @@ export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => { }, }) - const { can: canResumeProject } = useAsyncCheckProjectPermissions( + const { can: canResumeProject } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_jobs.projects.initialize_or_resume' ) diff --git a/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx b/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx index edb62abd66c45..401a91b6e8421 100644 --- a/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/RestoreFailedState.tsx @@ -10,7 +10,7 @@ import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { InlineLink } from 'components/ui/InlineLink' import { useBackupDownloadMutation } from 'data/database/backup-download-mutation' import { useDownloadableBackupQuery } from 'data/database/backup-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, CriticalIcon, DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from 'ui' @@ -19,11 +19,9 @@ export const RestoreFailedState = () => { const { data: project } = useSelectedProjectQuery() const [visible, setVisible] = useState(false) - const { can: canDeleteProject } = useAsyncCheckProjectPermissions( - PermissionAction.UPDATE, - 'projects', - { resource: { project_id: project?.id } } - ) + const { can: canDeleteProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', { + resource: { project_id: project?.id }, + }) const { data } = useDownloadableBackupQuery({ projectRef: ref }) const backups = data?.backups ?? [] diff --git a/apps/studio/components/layouts/ReportsLayout/ReportMenuItem.tsx b/apps/studio/components/layouts/ReportsLayout/ReportMenuItem.tsx index 3ccba9049a7c3..781a4080ba2f3 100644 --- a/apps/studio/components/layouts/ReportsLayout/ReportMenuItem.tsx +++ b/apps/studio/components/layouts/ReportsLayout/ReportMenuItem.tsx @@ -3,7 +3,7 @@ import { ChevronDown, Edit2, Trash } from 'lucide-react' import Link from 'next/link' import { ContentBase } from 'data/content/content-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useProfile } from 'lib/profile' import { Dashboards } from 'types' import { @@ -41,7 +41,7 @@ export const ReportMenuItem = ({ onSelectDelete, }: ReportMenuItemProps) => { const { profile } = useProfile() - const { can: canUpdateCustomReport } = useAsyncCheckProjectPermissions( + const { can: canUpdateCustomReport } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'user_content', { diff --git a/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx b/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx index a0e1a4f5d4abc..bdcbc96054ff8 100644 --- a/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx +++ b/apps/studio/components/layouts/ReportsLayout/ReportsMenu.tsx @@ -12,7 +12,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useContentDeleteMutation } from 'data/content/content-delete-mutation' import { Content, useContentQuery } from 'data/content/content-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useProfile } from 'lib/profile' import { Menu, cn } from 'ui' @@ -34,7 +34,7 @@ const ReportsMenu = () => { const storageSupported = useIsFeatureEnabled('project_storage:all') const storageEnabled = storageReportEnabled && storageSupported - const { can: canCreateCustomReport } = useAsyncCheckProjectPermissions( + const { can: canCreateCustomReport } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index bbc045e308314..a6af02d7eef3d 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -6,7 +6,7 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useLocalStorage } from 'hooks/misc/useLocalStorage' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProfile } from 'lib/profile' @@ -48,7 +48,7 @@ export const SQLEditorMenu = () => { const appState = getAppStateSnapshot() const debouncedSearch = useDebounce(search, 500) - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx index 4dd78e8b07cbd..e22c2d522138b 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorNavV2/SQLEditorTreeViewItem.tsx @@ -21,7 +21,7 @@ import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLE import { getContentById } from 'data/content/content-id-query' import { useSQLSnippetFolderContentsQuery } from 'data/content/sql-folder-contents-query' import { Snippet } from 'data/content/sql-folders-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import useLatest from 'hooks/misc/useLatest' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProfile } from 'lib/profile' @@ -106,7 +106,7 @@ export const SQLEditorTreeViewItem = ({ const isEditing = status === 'editing' const isSaving = status === 'saving' - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/layouts/SQLEditorLayout/SqlEditor.Commands.tsx b/apps/studio/components/layouts/SQLEditorLayout/SqlEditor.Commands.tsx index dc7be6eb36363..0f975237051c7 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SqlEditor.Commands.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SqlEditor.Commands.tsx @@ -9,7 +9,7 @@ import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/Com import { orderCommandSectionsByPriority } from 'components/interfaces/App/CommandMenu/ordering' import { useSqlSnippetsQuery, type SqlSnippet } from 'data/content/sql-snippets-query' import { usePrefetchTables, useTablesQuery, type TablesData } from 'data/tables/tables-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProtectedSchemas } from 'hooks/useProtectedSchemas' import { useProfile } from 'lib/profile' @@ -103,7 +103,7 @@ function RunSnippetPage() { const snippets = snippetPages?.pages.flatMap((page) => page.contents) const { profile } = useProfile() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index 2727eb420285f..e7615f9f4b981 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -2,11 +2,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { PropsWithChildren } from 'react' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { ProjectLayoutWithAuth } from '../ProjectLayout/ProjectLayout' const TableEditorLayout = ({ children }: PropsWithChildren<{}>) => { - const { can: canReadTables, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadTables, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'tables' ) diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 929db988b7c5f..59d87733ade01 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -16,7 +16,7 @@ import SchemaSelector from 'components/ui/SchemaSelector' import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' import { useEntityTypesQuery } from 'data/entity-types/entity-types-infinite-query' import { getTableEditor, useTableEditorQuery } from 'data/table-editor/table-editor-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useLocalStorage } from 'hooks/misc/useLocalStorage' import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' @@ -85,7 +85,7 @@ export const TableEditorMenu = () => { [data?.pages] ) - const { can: canCreateTables } = useAsyncCheckProjectPermissions( + const { can: canCreateTables } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) diff --git a/apps/studio/components/layouts/Tabs/NewTab.tsx b/apps/studio/components/layouts/Tabs/NewTab.tsx index 182b85e96645b..c982690727667 100644 --- a/apps/studio/components/layouts/Tabs/NewTab.tsx +++ b/apps/studio/components/layouts/Tabs/NewTab.tsx @@ -9,7 +9,7 @@ import { useParams } from 'common' import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.queries' import { createSqlSnippetSkeletonV2 } from 'components/interfaces/SQLEditor/SQLEditor.utils' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { uuidv4 } from 'lib/helpers' @@ -46,7 +46,7 @@ export function NewTab() { const [quickstarts] = partition(SQL_TEMPLATES, { type: 'quickstart' }) const { mutate: sendEvent } = useSendEventMutation() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx b/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx index 919b847f50b26..70f7a91360222 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIOptInModal.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react' import { AIOptInLevelSelector } from 'components/interfaces/Organization/GeneralSettings/AIOptInLevelSelector' import { useAIOptInForm } from 'hooks/forms/useAIOptInForm' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Button, cn, @@ -24,7 +24,7 @@ interface AIOptInModalProps { export const AIOptInModal = ({ visible, onCancel }: AIOptInModalProps) => { const { form, onSubmit, isUpdating, currentOptInLevel } = useAIOptInForm(onCancel) - const { can: canUpdateOrganization } = useAsyncCheckProjectPermissions( + const { can: canUpdateOrganization } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx b/apps/studio/components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx index deb418a749ecf..a36502f0bfa38 100644 --- a/apps/studio/components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/DisplayBlockRenderer.tsx @@ -6,7 +6,7 @@ import { DragEvent, PropsWithChildren, useMemo, useState } from 'react' import { useParams } from 'common' import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useProfile } from 'lib/profile' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' @@ -48,7 +48,7 @@ export const DisplayBlockRenderer = ({ const snap = useAiAssistantStateSnapshot() const { mutate: sendEvent } = useSendEventMutation() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx index f3acd11c622a2..7764136b47f3e 100644 --- a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx @@ -13,7 +13,7 @@ import { import { ChartConfig } from 'components/interfaces/SQLEditor/UtilityPanel/ChartConfig' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useProfile } from 'lib/profile' @@ -232,7 +232,7 @@ export const MarkdownPre = ({ const { data: project } = useSelectedProjectQuery() const { data: org } = useSelectedOrganizationQuery() - const { can: canCreateSQLSnippet } = useAsyncCheckProjectPermissions( + const { can: canCreateSQLSnippet } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx index 0ef28a3426f79..80277032385cb 100644 --- a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx +++ b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx @@ -9,7 +9,7 @@ import { useParams } from 'common' import Panel from 'components/ui/Panel' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Input } from 'ui' import { getLastUsedAPIKeys, useLastUsedAPIKeysLogQuery } from './DisplayApiSettings.utils' @@ -36,7 +36,7 @@ export const DisplayApiSettings = ({ } = useJwtSecretUpdatingStatusQuery({ projectRef }) const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus - const { isLoading: isLoadingPermissions, can: canReadAPIKeys } = useAsyncCheckProjectPermissions( + const { isLoading: isLoadingPermissions, can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.READ, 'service_api_keys' ) diff --git a/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx b/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx index 19b67e34cc173..8998ab5c14591 100644 --- a/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx +++ b/apps/studio/components/ui/ProjectSettings/ToggleLegacyApiKeys.tsx @@ -8,7 +8,7 @@ import { useToggleLegacyAPIKeysMutation } from 'data/api-keys/legacy-api-key-tog import { useLegacyAPIKeysStatusQuery } from 'data/api-keys/legacy-api-keys-status-query' import { useLegacyJWTSigningKeyQuery } from 'data/jwt-signing-keys/legacy-jwt-signing-key-query' import { useAuthorizedAppsQuery } from 'data/oauth/authorized-apps-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { AlertDialog, @@ -35,8 +35,10 @@ export const ToggleLegacyApiKeysPanel = () => { const { data: legacyJWTSecret } = useLegacyJWTSigningKeyQuery({ projectRef }) - const { can: canUpdateAPIKeys, isSuccess: isPermissionsSuccess } = - useAsyncCheckProjectPermissions(PermissionAction.SECRETS_WRITE, '*') + const { can: canUpdateAPIKeys, isSuccess: isPermissionsSuccess } = useAsyncCheckPermissions( + PermissionAction.SECRETS_WRITE, + '*' + ) const { data: authorizedApps = [], isSuccess: isAuthorizedAppsSuccess } = useAuthorizedAppsQuery({ slug: org?.slug, diff --git a/apps/studio/components/ui/SchemaSelector.tsx b/apps/studio/components/ui/SchemaSelector.tsx index 72669f80ffedc..e3f89590ff23d 100644 --- a/apps/studio/components/ui/SchemaSelector.tsx +++ b/apps/studio/components/ui/SchemaSelector.tsx @@ -3,7 +3,7 @@ import { Check, ChevronsUpDown, Plus } from 'lucide-react' import { useState } from 'react' import { useSchemasQuery } from 'data/database/schemas-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { AlertDescription_Shadcn_, @@ -52,7 +52,7 @@ const SchemaSelector = ({ align = 'start', }: SchemaSelectorProps) => { const [open, setOpen] = useState(false) - const { can: canCreateSchemas } = useAsyncCheckProjectPermissions( + const { can: canCreateSchemas } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'schemas' ) diff --git a/apps/studio/components/ui/UpgradeToPro.tsx b/apps/studio/components/ui/UpgradeToPro.tsx index 8e4b2a9d96622..7cf9aa395238a 100644 --- a/apps/studio/components/ui/UpgradeToPro.tsx +++ b/apps/studio/components/ui/UpgradeToPro.tsx @@ -3,7 +3,7 @@ import Link from 'next/link' import { ReactNode } from 'react' import { useFlag } from 'common' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, cn } from 'ui' @@ -34,7 +34,7 @@ const UpgradeToPro = ({ const { data: organization } = useSelectedOrganizationQuery() const plan = organization?.plan?.id - const { can: canUpdateSubscription } = useAsyncCheckProjectPermissions( + const { can: canUpdateSubscription } = useAsyncCheckPermissions( PermissionAction.BILLING_WRITE, 'stripe.subscriptions' ) diff --git a/apps/studio/data/config/project-settings-v2-query.ts b/apps/studio/data/config/project-settings-v2-query.ts index 1bba0b1806a43..759c0a784c732 100644 --- a/apps/studio/data/config/project-settings-v2-query.ts +++ b/apps/studio/data/config/project-settings-v2-query.ts @@ -3,7 +3,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import type { components } from 'data/api' import { get, handleError } from 'data/fetchers' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { ResponseError } from 'types' import { configKeys } from './keys' @@ -44,7 +44,7 @@ export const useProjectSettingsV2Query = ( ) => { // [Joshen] Sync with API perms checking here - shouldReturnApiKeys // https://github.com/supabase/infrastructure/blob/develop/api/src/routes/platform/projects/ref/settings.controller.ts#L92 - const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, '*' ) diff --git a/apps/studio/data/organizations/organization-customer-profile-query.ts b/apps/studio/data/organizations/organization-customer-profile-query.ts index b95c9a7a15efe..7fe953a0a17e2 100644 --- a/apps/studio/data/organizations/organization-customer-profile-query.ts +++ b/apps/studio/data/organizations/organization-customer-profile-query.ts @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { get, handleError } from 'data/fetchers' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { ResponseError } from 'types' import { organizationKeys } from './keys' @@ -43,7 +43,7 @@ export const useOrganizationCustomerProfileQuery = { // [Joshen] Thinking it makes sense to add this check at the RQ level - prevent // unnecessary requests, although this behaviour still needs handling on the UI - const { can: canReadCustomerProfile } = useAsyncCheckProjectPermissions( + const { can: canReadCustomerProfile } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.customer' ) diff --git a/apps/studio/data/organizations/organization-payment-methods-query.ts b/apps/studio/data/organizations/organization-payment-methods-query.ts index 800fd895f0f25..cc22ddd36c5b7 100644 --- a/apps/studio/data/organizations/organization-payment-methods-query.ts +++ b/apps/studio/data/organizations/organization-payment-methods-query.ts @@ -3,7 +3,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { components } from 'api-types' import { get, handleError } from 'data/fetchers' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { ResponseError } from 'types' import { organizationKeys } from './keys' @@ -44,7 +44,7 @@ export const useOrganizationPaymentMethodsQuery = = {} ) => { - const { can: canReadSubscriptions } = useAsyncCheckProjectPermissions( + const { can: canReadSubscriptions } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.payment_methods' ) diff --git a/apps/studio/data/organizations/organization-tax-id-query.ts b/apps/studio/data/organizations/organization-tax-id-query.ts index 06e7ca658964a..ede1efe0c2f0c 100644 --- a/apps/studio/data/organizations/organization-tax-id-query.ts +++ b/apps/studio/data/organizations/organization-tax-id-query.ts @@ -3,7 +3,7 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { components } from 'api-types' import { get, handleError } from 'data/fetchers' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { ResponseError } from 'types' import { organizationKeys } from './keys' @@ -36,7 +36,7 @@ export const useOrganizationTaxIdQuery = ( ...options }: UseQueryOptions = {} ) => { - const { can: canReadSubscriptions } = useAsyncCheckProjectPermissions( + const { can: canReadSubscriptions } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.tax_ids' ) diff --git a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts index ed59e90e08c85..56752fd634651 100644 --- a/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts +++ b/apps/studio/data/storage/iceberg-wrapper-create-mutation.ts @@ -9,7 +9,7 @@ import { import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { FDWCreateVariables, useFDWCreateMutation } from 'data/fdw/fdw-create-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useS3AccessKeyCreateMutation } from './s3-access-key-create-mutation' @@ -27,7 +27,7 @@ export const useIcebergWrapperCreateMutation = () => { const wrapperMeta = WRAPPERS.find((wrapper) => wrapper.name === 'iceberg_wrapper') - const { can: canCreateCredentials } = useAsyncCheckProjectPermissions( + const { can: canCreateCredentials } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_WRITE, '*' ) diff --git a/apps/studio/data/subscriptions/org-plans-query.ts b/apps/studio/data/subscriptions/org-plans-query.ts index a79e76e95272c..ab1a68ee525f9 100644 --- a/apps/studio/data/subscriptions/org-plans-query.ts +++ b/apps/studio/data/subscriptions/org-plans-query.ts @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { get, handleError } from 'data/fetchers' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { subscriptionKeys } from './keys' export type OrgPlansVariables = { @@ -27,7 +27,7 @@ export const useOrgPlansQuery = ( { orgSlug }: OrgPlansVariables, { enabled = true, ...options }: UseQueryOptions = {} ) => { - const { can: canReadSubscriptions } = useAsyncCheckProjectPermissions( + const { can: canReadSubscriptions } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.subscriptions' ) diff --git a/apps/studio/data/subscriptions/org-subscription-query.ts b/apps/studio/data/subscriptions/org-subscription-query.ts index a91e6f29e7fe0..c2d068bbbec9d 100644 --- a/apps/studio/data/subscriptions/org-subscription-query.ts +++ b/apps/studio/data/subscriptions/org-subscription-query.ts @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQuery, UseQueryOptions } from '@tanstack/react-query' import { get, handleError } from 'data/fetchers' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { ResponseError } from 'types' import { subscriptionKeys } from './keys' @@ -37,7 +37,7 @@ export const useOrgSubscriptionQuery = ( ) => { // [Joshen] Thinking it makes sense to add this check at the RQ level - prevent // unnecessary requests, although this behaviour still needs handling on the UI - const { can: canReadSubscriptions } = useAsyncCheckProjectPermissions( + const { can: canReadSubscriptions } = useAsyncCheckPermissions( PermissionAction.BILLING_READ, 'stripe.subscriptions' ) diff --git a/apps/studio/hooks/forms/useAIOptInForm.ts b/apps/studio/hooks/forms/useAIOptInForm.ts index 9a85468fb1777..10f94b86252e1 100644 --- a/apps/studio/hooks/forms/useAIOptInForm.ts +++ b/apps/studio/hooks/forms/useAIOptInForm.ts @@ -8,7 +8,7 @@ import * as z from 'zod' import { LOCAL_STORAGE_KEYS } from 'common' import { useOrganizationUpdateMutation } from 'data/organizations/organization-update-mutation' import { invalidateOrganizationsQuery } from 'data/organizations/organizations-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { getAiOptInLevel } from 'hooks/misc/useOrgOptedIntoAi' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' @@ -31,7 +31,7 @@ export type AIOptInFormValues = z.infer export const useAIOptInForm = (onSuccessCallback?: () => void) => { const queryClient = useQueryClient() const { data: selectedOrganization } = useSelectedOrganizationQuery() - const { can: canUpdateOrganization } = useAsyncCheckProjectPermissions( + const { can: canUpdateOrganization } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'organizations' ) diff --git a/apps/studio/hooks/misc/useCheckPermissions.ts b/apps/studio/hooks/misc/useCheckPermissions.ts index 15437b5f3c980..8b2106c3d58d6 100644 --- a/apps/studio/hooks/misc/useCheckPermissions.ts +++ b/apps/studio/hooks/misc/useCheckPermissions.ts @@ -1,9 +1,7 @@ import { useIsLoggedIn, useParams } from 'common' import jsonLogic from 'json-logic-js' -import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { usePermissionsQuery } from 'data/permissions/permissions-query' -import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { IS_PLATFORM } from 'lib/constants' import type { Permission } from 'types' import { useSelectedOrganizationQuery } from './useSelectedOrganization' @@ -74,7 +72,7 @@ export function useGetPermissions( return useGetProjectPermissions(permissionsOverride, organizationSlugOverride, undefined, enabled) } -export function useGetProjectPermissions( +function useGetProjectPermissions( permissionsOverride?: Permission[], organizationSlugOverride?: string, projectRefOverride?: string, @@ -135,78 +133,9 @@ export function useGetProjectPermissions( } } -/** - * @deprecated If checking for project permissions, use useAsyncCheckProjectPermissions instead so that we can always - * check for loading states to not prematurely show "no perms" UIs. We'll also need a separate async check for org perms too - * - * Use `import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'` instead - * [Joshen] No longer being used, can be deprecated in follow up PR - */ -export function useCheckPermissions( - action: string, - resource: string, - data?: object, - // [Joshen] Pass the variables if you want to avoid hooks in this - // e.g If you want to use useCheckPermissions in a loop like organization settings - organizationSlug?: string, - permissions?: Permission[] -) { - return useCheckProjectPermissions(action, resource, data, { - organizationSlug, - projectRef: undefined, - permissions, - }) -} - -export function useCheckProjectPermissions( - action: string, - resource: string, - data?: object, - overrides?: { - organizationSlug?: string - projectRef?: string - permissions?: Permission[] - } -) { - const isLoggedIn = useIsLoggedIn() - const { organizationSlug, projectRef, permissions } = overrides ?? {} - - const { - permissions: allPermissions, - organizationSlug: _organizationSlug, - projectRef: _projectRef, - } = useGetProjectPermissions(permissions, organizationSlug, projectRef, isLoggedIn) - - if (!isLoggedIn) return false - if (!IS_PLATFORM) return true - - return doPermissionsCheck(allPermissions, action, resource, data, _organizationSlug, _projectRef) -} - -/** [Joshen] No longer being used, can be deprecated in follow up PR */ -export function usePermissionsLoaded() { - const isLoggedIn = useIsLoggedIn() - const { isFetched: isPermissionsFetched } = usePermissionsQuery({ enabled: isLoggedIn }) - const { isFetched: isOrganizationsFetched } = useOrganizationsQuery({ enabled: isLoggedIn }) - - const { ref } = useParams() - const { isFetched: isProjectDetailFetched } = useProjectDetailQuery( - { ref }, - { enabled: !!ref && isLoggedIn } - ) - - if (!IS_PLATFORM) return true - - if (ref) { - return isLoggedIn && isPermissionsFetched && isOrganizationsFetched && isProjectDetailFetched - } - - return isLoggedIn && isPermissionsFetched && isOrganizationsFetched -} - /** [Joshen] To be renamed to be useAsyncCheckPermissions, more generic as it covers both org and project perms */ // Useful when you want to avoid layout changes while waiting for permissions to load -export function useAsyncCheckProjectPermissions( +export function useAsyncCheckPermissions( action: string, resource: string, data?: object, diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx index beaa6dacab5d7..c1c01b8478371 100644 --- a/apps/studio/pages/new/[slug].tsx +++ b/apps/studio/pages/new/[slug].tsx @@ -49,7 +49,7 @@ import { import { useProjectsQuery } from 'data/projects/projects-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCustomContent } from 'hooks/custom-content/useCustomContent' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' @@ -260,7 +260,7 @@ const Wizard: NextPageWithLayout = () => { ? availableRegionsData?.recommendations.smartGroup.name : _defaultRegion - const { can: isAdmin } = useAsyncCheckProjectPermissions(PermissionAction.CREATE, 'projects') + const { can: isAdmin } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') const isInvalidSlug = isOrganizationsSuccess && currentOrg === undefined const orgNotFound = isOrganizationsSuccess && (organizations?.length ?? 0) > 0 && isInvalidSlug diff --git a/apps/studio/pages/project/[ref]/auth/advanced.tsx b/apps/studio/pages/project/[ref]/auth/advanced.tsx index 254e8c0721d89..923f701430566 100644 --- a/apps/studio/pages/project/[ref]/auth/advanced.tsx +++ b/apps/studio/pages/project/[ref]/auth/advanced.tsx @@ -9,7 +9,7 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { UnknownInterface } from 'components/ui/UnknownInterface' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' @@ -17,8 +17,10 @@ const AdvancedPage: NextPageWithLayout = () => { const { ref } = useParams() const showAdvanced = useIsFeatureEnabled('authentication:advanced') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (!showAdvanced) { return diff --git a/apps/studio/pages/project/[ref]/auth/audit-logs.tsx b/apps/studio/pages/project/[ref]/auth/audit-logs.tsx index 0b3554b0beed0..6f955ce1ec1de 100644 --- a/apps/studio/pages/project/[ref]/auth/audit-logs.tsx +++ b/apps/studio/pages/project/[ref]/auth/audit-logs.tsx @@ -10,14 +10,16 @@ import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const AuditLogsPage: NextPageWithLayout = () => { const { ref: projectRef } = useParams() - const isPermissionsLoaded = usePermissionsLoaded() const { isLoading: isLoadingConfig } = useAuthConfigQuery({ projectRef }) - const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (isPermissionsLoaded && !canReadAuthSettings) { return diff --git a/apps/studio/pages/project/[ref]/auth/hooks.tsx b/apps/studio/pages/project/[ref]/auth/hooks.tsx index ebea0d189d3a1..023327154fa03 100644 --- a/apps/studio/pages/project/[ref]/auth/hooks.tsx +++ b/apps/studio/pages/project/[ref]/auth/hooks.tsx @@ -7,13 +7,15 @@ import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' import { GenericSkeletonLoader } from 'ui-patterns' const Hooks: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (isPermissionsLoaded && !canReadAuthSettings) { return diff --git a/apps/studio/pages/project/[ref]/auth/mfa.tsx b/apps/studio/pages/project/[ref]/auth/mfa.tsx index 30844c34b806c..d6919211c3120 100644 --- a/apps/studio/pages/project/[ref]/auth/mfa.tsx +++ b/apps/studio/pages/project/[ref]/auth/mfa.tsx @@ -9,7 +9,7 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { UnknownInterface } from 'components/ui/UnknownInterface' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' @@ -17,8 +17,10 @@ const MfaPage: NextPageWithLayout = () => { const { ref } = useParams() const showMFA = useIsFeatureEnabled('authentication:multi_factor') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (!showMFA) { return diff --git a/apps/studio/pages/project/[ref]/auth/policies.tsx b/apps/studio/pages/project/[ref]/auth/policies.tsx index c66c3b62ae93b..cc3bd1269d56a 100644 --- a/apps/studio/pages/project/[ref]/auth/policies.tsx +++ b/apps/studio/pages/project/[ref]/auth/policies.tsx @@ -20,7 +20,7 @@ import SchemaSelector from 'components/ui/SchemaSelector' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabasePoliciesQuery } from 'data/database-policies/database-policies-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useUrlState } from 'hooks/ui/useUrlState' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' @@ -98,7 +98,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { }) const filteredTables = onFilterTables(tables ?? [], policies ?? [], searchString) - const { can: canReadPolicies, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadPolicies, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'policies' ) diff --git a/apps/studio/pages/project/[ref]/auth/protection.tsx b/apps/studio/pages/project/[ref]/auth/protection.tsx index 6a18dee5f6056..efd6eec56a1c7 100644 --- a/apps/studio/pages/project/[ref]/auth/protection.tsx +++ b/apps/studio/pages/project/[ref]/auth/protection.tsx @@ -9,7 +9,7 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { UnknownInterface } from 'components/ui/UnknownInterface' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' @@ -17,8 +17,10 @@ const ProtectionPage: NextPageWithLayout = () => { const { ref } = useParams() const showAttackProtection = useIsFeatureEnabled('authentication:attack_protection') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (!showAttackProtection) { return diff --git a/apps/studio/pages/project/[ref]/auth/rate-limits.tsx b/apps/studio/pages/project/[ref]/auth/rate-limits.tsx index 9c736babb4d41..4b6d186e64dc4 100644 --- a/apps/studio/pages/project/[ref]/auth/rate-limits.tsx +++ b/apps/studio/pages/project/[ref]/auth/rate-limits.tsx @@ -10,7 +10,7 @@ import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { UnknownInterface } from 'components/ui/UnknownInterface' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' @@ -18,8 +18,10 @@ const RateLimitsPage: NextPageWithLayout = () => { const { ref } = useParams() const showRateLimits = useIsFeatureEnabled('authentication:rate_limits') - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (!showRateLimits) { return diff --git a/apps/studio/pages/project/[ref]/auth/sessions.tsx b/apps/studio/pages/project/[ref]/auth/sessions.tsx index 683b111196f8e..eb22314bb1da2 100644 --- a/apps/studio/pages/project/[ref]/auth/sessions.tsx +++ b/apps/studio/pages/project/[ref]/auth/sessions.tsx @@ -7,12 +7,14 @@ import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const SessionsPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (isPermissionsLoaded && !canReadAuthSettings) { return diff --git a/apps/studio/pages/project/[ref]/auth/smtp.tsx b/apps/studio/pages/project/[ref]/auth/smtp.tsx index b6bd764a9081c..ba95e4c9eed40 100644 --- a/apps/studio/pages/project/[ref]/auth/smtp.tsx +++ b/apps/studio/pages/project/[ref]/auth/smtp.tsx @@ -5,13 +5,15 @@ import { AuthEmailsLayout } from 'components/layouts/AuthLayout/AuthEmailsLayout import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' import { GenericSkeletonLoader } from 'ui-patterns' const SmtpPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (isPermissionsLoaded && !canReadAuthSettings) { return diff --git a/apps/studio/pages/project/[ref]/auth/templates.tsx b/apps/studio/pages/project/[ref]/auth/templates.tsx index 8ac5ec3aacd0e..cc1ccbaa0fed5 100644 --- a/apps/studio/pages/project/[ref]/auth/templates.tsx +++ b/apps/studio/pages/project/[ref]/auth/templates.tsx @@ -5,13 +5,15 @@ import { AuthEmailsLayout } from 'components/layouts/AuthLayout/AuthEmailsLayout import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' import { GenericSkeletonLoader } from 'ui-patterns' const TemplatesPage: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (isPermissionsLoaded && !canReadAuthSettings) { return diff --git a/apps/studio/pages/project/[ref]/auth/third-party.tsx b/apps/studio/pages/project/[ref]/auth/third-party.tsx index fac8db8e2a318..6e989a5d6c58c 100644 --- a/apps/studio/pages/project/[ref]/auth/third-party.tsx +++ b/apps/studio/pages/project/[ref]/auth/third-party.tsx @@ -8,14 +8,16 @@ import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { UnknownInterface } from 'components/ui/UnknownInterface' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const ThirdPartyPage: NextPageWithLayout = () => { const { ref } = useParams() - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) const showThirdPartyAuth = useIsFeatureEnabled('authentication:third_party_auth') diff --git a/apps/studio/pages/project/[ref]/auth/url-configuration.tsx b/apps/studio/pages/project/[ref]/auth/url-configuration.tsx index 81df0a87cd9be..442d9e9e25727 100644 --- a/apps/studio/pages/project/[ref]/auth/url-configuration.tsx +++ b/apps/studio/pages/project/[ref]/auth/url-configuration.tsx @@ -8,12 +8,14 @@ import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const URLConfiguration: NextPageWithLayout = () => { - const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) if (isPermissionsLoaded && !canReadAuthSettings) { return diff --git a/apps/studio/pages/project/[ref]/branches/index.tsx b/apps/studio/pages/project/[ref]/branches/index.tsx index 006d7b5ff28b0..30cb0df93356d 100644 --- a/apps/studio/pages/project/[ref]/branches/index.tsx +++ b/apps/studio/pages/project/[ref]/branches/index.tsx @@ -19,7 +19,7 @@ import { useBranchDeleteMutation } from 'data/branches/branch-delete-mutation' import { Branch, useBranchesQuery } from 'data/branches/branches-query' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useAppStateSnapshot } from 'state/app-state' @@ -42,7 +42,7 @@ const BranchesPage: NextPageWithLayout = () => { const projectRef = project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined - const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.READ, 'preview_branches' ) @@ -186,7 +186,7 @@ const BranchesPage: NextPageWithLayout = () => { BranchesPage.getLayout = (page) => { const BranchesPageWrapper = () => { const snap = useAppStateSnapshot() - const { can: canCreateBranches } = useAsyncCheckProjectPermissions( + const { can: canCreateBranches } = useAsyncCheckPermissions( PermissionAction.CREATE, 'preview_branches', { diff --git a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx index 749b79d91607e..d66359dc3796b 100644 --- a/apps/studio/pages/project/[ref]/branches/merge-requests.tsx +++ b/apps/studio/pages/project/[ref]/branches/merge-requests.tsx @@ -24,7 +24,7 @@ import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import { Branch, useBranchesQuery } from 'data/branches/branches-query' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import type { NextPageWithLayout } from 'types' @@ -49,7 +49,7 @@ const MergeRequestsPage: NextPageWithLayout = () => { const projectRef = project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined - const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadBranches, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.READ, 'preview_branches' ) diff --git a/apps/studio/pages/project/[ref]/database/backups/pitr.tsx b/apps/studio/pages/project/[ref]/database/backups/pitr.tsx index ae534f92f1320..94679b8eeb651 100644 --- a/apps/studio/pages/project/[ref]/database/backups/pitr.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/pitr.tsx @@ -14,7 +14,7 @@ import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useBackupsQuery } from 'data/database/backups-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useIsOrioleDbInAws, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' @@ -57,8 +57,10 @@ const PITR = () => { const isEnabled = backups?.pitr_enabled const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'physical_backups') + const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'physical_backups' + ) if (isPermissionsLoaded && !canReadPhysicalBackups) { return diff --git a/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx b/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx index 31a58c8bccb4a..56016f6392c3d 100644 --- a/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/restore-to-new-project.tsx @@ -23,7 +23,7 @@ import UpgradeToPro from 'components/ui/UpgradeToPro' import { useDiskAttributesQuery } from 'data/config/disk-attributes-query' import { useCloneBackupsQuery } from 'data/projects/clone-query' import { useCloneStatusQuery } from 'data/projects/clone-status-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useIsAwsK8sCloudProvider, @@ -82,9 +82,11 @@ const RestoreToNewProject = () => { const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'physical_backups') - const { can: canTriggerPhysicalBackups } = useAsyncCheckProjectPermissions( + const { can: canReadPhysicalBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'physical_backups' + ) + const { can: canTriggerPhysicalBackups } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.restore.prepare' ) diff --git a/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx b/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx index 52b0838add596..b0a3b0c6c8000 100644 --- a/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx +++ b/apps/studio/pages/project/[ref]/database/backups/scheduled.tsx @@ -14,7 +14,7 @@ import InformationBox from 'components/ui/InformationBox' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useBackupsQuery } from 'data/database/backups-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsOrioleDbInAws } from 'hooks/misc/useSelectedProject' import type { NextPageWithLayout } from 'types' import { Admonition } from 'ui-patterns' @@ -27,8 +27,10 @@ const DatabaseScheduledBackups: NextPageWithLayout = () => { const isOrioleDbInAws = useIsOrioleDbInAws() const isPitrEnabled = backups?.pitr_enabled - const { can: canReadScheduledBackups, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.READ, 'back_ups') + const { can: canReadScheduledBackups, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.READ, + 'back_ups' + ) return ( diff --git a/apps/studio/pages/project/[ref]/database/extensions.tsx b/apps/studio/pages/project/[ref]/database/extensions.tsx index 01bc3ba3e841c..51b06d46f713a 100644 --- a/apps/studio/pages/project/[ref]/database/extensions.tsx +++ b/apps/studio/pages/project/[ref]/database/extensions.tsx @@ -6,12 +6,14 @@ import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const DatabaseExtensions: NextPageWithLayout = () => { - const { can: canReadExtensions, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'extensions') + const { can: canReadExtensions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_READ, + 'extensions' + ) if (isPermissionsLoaded && !canReadExtensions) { return diff --git a/apps/studio/pages/project/[ref]/database/functions.tsx b/apps/studio/pages/project/[ref]/database/functions.tsx index fc74e83002ada..1ab76e36bb859 100644 --- a/apps/studio/pages/project/[ref]/database/functions.tsx +++ b/apps/studio/pages/project/[ref]/database/functions.tsx @@ -11,7 +11,7 @@ import { EditorPanel } from 'components/ui/EditorPanel/EditorPanel' import { FormHeader } from 'components/ui/Forms/FormHeader' import NoPermission from 'components/ui/NoPermission' import { DatabaseFunction } from 'data/database-functions/database-functions-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const DatabaseFunctionsPage: NextPageWithLayout = () => { @@ -26,7 +26,7 @@ const DatabaseFunctionsPage: NextPageWithLayout = () => { DatabaseFunction | undefined >() - const { can: canReadFunctions, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadFunctions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'functions' ) diff --git a/apps/studio/pages/project/[ref]/database/publications/[id].tsx b/apps/studio/pages/project/[ref]/database/publications/[id].tsx index 81fec4d623a87..2905ae2a7c184 100644 --- a/apps/studio/pages/project/[ref]/database/publications/[id].tsx +++ b/apps/studio/pages/project/[ref]/database/publications/[id].tsx @@ -6,12 +6,14 @@ import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const DatabasePublications: NextPageWithLayout = () => { - const { can: canViewPublications, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'publications') + const { can: canViewPublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_READ, + 'publications' + ) if (isPermissionsLoaded && !canViewPublications) { return diff --git a/apps/studio/pages/project/[ref]/database/publications/index.tsx b/apps/studio/pages/project/[ref]/database/publications/index.tsx index b5ee05c33dcd2..ddf605455a42c 100644 --- a/apps/studio/pages/project/[ref]/database/publications/index.tsx +++ b/apps/studio/pages/project/[ref]/database/publications/index.tsx @@ -6,12 +6,14 @@ import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const DatabasePublications: NextPageWithLayout = () => { - const { can: canViewPublications, isSuccess: isPermissionsLoaded } = - useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'publications') + const { can: canViewPublications, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( + PermissionAction.TENANT_SQL_ADMIN_READ, + 'publications' + ) if (isPermissionsLoaded && !canViewPublications) { return diff --git a/apps/studio/pages/project/[ref]/database/triggers.tsx b/apps/studio/pages/project/[ref]/database/triggers.tsx index 4f90286f184f6..e483e2f752c5a 100644 --- a/apps/studio/pages/project/[ref]/database/triggers.tsx +++ b/apps/studio/pages/project/[ref]/database/triggers.tsx @@ -13,7 +13,7 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { EditorPanel } from 'components/ui/EditorPanel/EditorPanel' import { FormHeader } from 'components/ui/Forms/FormHeader' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const TriggersPage: NextPageWithLayout = () => { @@ -27,7 +27,7 @@ const TriggersPage: NextPageWithLayout = () => { const [editorPanelOpen, setEditorPanelOpen] = useState(false) const [selectedTriggerForEditor, setSelectedTriggerForEditor] = useState() - const { can: canReadTriggers, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadTriggers, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_READ, 'triggers' ) diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx index 21ab897ec4445..2de8d35300607 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/code.tsx @@ -14,7 +14,7 @@ import { useEdgeFunctionBodyQuery } from 'data/edge-functions/edge-function-body import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useEdgeFunctionDeployMutation } from 'data/edge-functions/edge-functions-deploy-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { BASE_PATH } from 'lib/constants' @@ -28,10 +28,7 @@ const CodePage = () => { const { mutate: sendEvent } = useSendEventMutation() const [showDeployWarning, setShowDeployWarning] = useState(false) - const { can: canDeployFunction } = useAsyncCheckProjectPermissions( - PermissionAction.FUNCTIONS_WRITE, - '*' - ) + const { can: canDeployFunction } = useAsyncCheckPermissions(PermissionAction.FUNCTIONS_WRITE, '*') const { data: selectedFunction } = useEdgeFunctionQuery({ projectRef: ref, slug: functionSlug }) const { diff --git a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx index dc9cd65fb22f2..e09f4959457a0 100644 --- a/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx +++ b/apps/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx @@ -7,6 +7,7 @@ import sumBy from 'lodash/sumBy' import { useRouter } from 'next/router' import { useMemo, useState } from 'react' +import { useFlag } from 'common' import ReportWidget from 'components/interfaces/Reports/ReportWidget' import DefaultLayout from 'components/layouts/DefaultLayout' import EdgeFunctionDetailsLayout from 'components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout' @@ -19,7 +20,7 @@ import { } from 'data/analytics/functions-combined-stats-query' import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { ChartIntervals, NextPageWithLayout } from 'types' import { AlertDescription_Shadcn_, @@ -28,7 +29,6 @@ import { Button, WarningIcon, } from 'ui' -import { useFlag } from 'common' const CHART_INTERVALS: ChartIntervals[] = [ { @@ -122,7 +122,7 @@ const PageLayout: NextPageWithLayout = () => { endDate.toISOString() ) - const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckProjectPermissions( + const { isLoading: permissionsLoading, can: canReadFunction } = useAsyncCheckPermissions( PermissionAction.FUNCTIONS_READ, functionSlug as string ) diff --git a/apps/studio/pages/project/[ref]/logs/auth-logs.tsx b/apps/studio/pages/project/[ref]/logs/auth-logs.tsx index 3992a84cf048f..cf03e3b2bd37f 100644 --- a/apps/studio/pages/project/[ref]/logs/auth-logs.tsx +++ b/apps/studio/pages/project/[ref]/logs/auth-logs.tsx @@ -4,13 +4,13 @@ import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' import DefaultLayout from 'components/layouts/DefaultLayout' import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' import NoPermission from 'components/ui/NoPermission' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import type { NextPageWithLayout } from 'types' const LogsPage: NextPageWithLayout = () => { const { data: project } = useSelectedProjectQuery() - const { can: canReadAuthLogs } = useAsyncCheckProjectPermissions( + const { can: canReadAuthLogs } = useAsyncCheckPermissions( PermissionAction.ANALYTICS_READ, 'logflare' ) diff --git a/apps/studio/pages/project/[ref]/reports/database.tsx b/apps/studio/pages/project/[ref]/reports/database.tsx index 959967b54aa7e..d792f2b9114b3 100644 --- a/apps/studio/pages/project/[ref]/reports/database.tsx +++ b/apps/studio/pages/project/[ref]/reports/database.tsx @@ -34,7 +34,7 @@ import { useMaxConnectionsQuery } from 'data/database/max-connections-query' import { usePgbouncerConfigQuery } from 'data/database/pgbouncer-config-query' import { getReportAttributes, getReportAttributesV2 } from 'data/reports/database-charts' import { useDatabaseReport } from 'data/reports/database-report-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useReportDateRange } from 'hooks/misc/useReportDateRange' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' @@ -107,7 +107,7 @@ const DatabaseUsage = () => { }) const { data: poolerConfig } = usePgbouncerConfigQuery({ projectRef: project?.ref }) - const { can: canUpdateDiskSizeConfig } = useAsyncCheckProjectPermissions( + const { can: canUpdateDiskSizeConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'projects', { diff --git a/apps/studio/pages/project/[ref]/reports/index.tsx b/apps/studio/pages/project/[ref]/reports/index.tsx index ff2cc6e12e676..3fa77b7124ce0 100644 --- a/apps/studio/pages/project/[ref]/reports/index.tsx +++ b/apps/studio/pages/project/[ref]/reports/index.tsx @@ -9,7 +9,7 @@ import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import { Loading } from 'components/ui/Loading' import { useContentQuery } from 'data/content/content-query' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useProfile } from 'lib/profile' import type { NextPageWithLayout } from 'types' @@ -36,7 +36,7 @@ export const UserReportPage: NextPageWithLayout = () => { } ) - const { can: canCreateReport } = useAsyncCheckProjectPermissions( + const { can: canCreateReport } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { diff --git a/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx b/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx index 99b866a2e9498..1013828e9ed5d 100644 --- a/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx +++ b/apps/studio/pages/project/[ref]/settings/jwt/signing-keys.tsx @@ -6,11 +6,11 @@ import JWTKeysLayout from 'components/layouts/JWTKeys/JWTKeysLayout' import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const JWTSigningKeysPage: NextPageWithLayout = () => { - const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + const { can: canReadAPIKeys, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.READ, 'auth_signing_keys' ) diff --git a/apps/studio/pages/project/[ref]/settings/log-drains.tsx b/apps/studio/pages/project/[ref]/settings/log-drains.tsx index cdd982e072b9e..b118cf9bccd92 100644 --- a/apps/studio/pages/project/[ref]/settings/log-drains.tsx +++ b/apps/studio/pages/project/[ref]/settings/log-drains.tsx @@ -18,15 +18,17 @@ import { DocsButton } from 'components/ui/DocsButton' import { useCreateLogDrainMutation } from 'data/log-drains/create-log-drain-mutation' import { LogDrainData, useLogDrainsQuery } from 'data/log-drains/log-drains-query' import { useUpdateLogDrainMutation } from 'data/log-drains/update-log-drain-mutation' -import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan' import type { NextPageWithLayout } from 'types' import { Alert_Shadcn_, Button } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' const LogDrainsSettings: NextPageWithLayout = () => { - const { can: canManageLogDrains, isLoading: isLoadingPermissions } = - useAsyncCheckProjectPermissions(PermissionAction.ANALYTICS_ADMIN_WRITE, 'logflare') + const { can: canManageLogDrains, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( + PermissionAction.ANALYTICS_ADMIN_WRITE, + 'logflare' + ) const [open, setOpen] = useState(false) const { ref } = useParams() as { ref: string } From 10cc742349eac3b39c0d6a09b5e3f4c597a61688 Mon Sep 17 00:00:00 2001 From: David <150087495+HarmonyV@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:33:47 +0200 Subject: [PATCH 6/6] Update auth-google.mdx (#36780) Update flutter section based on google_sign_in 7.x --- .../guides/auth/social-login/auth-google.mdx | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/docs/content/guides/auth/social-login/auth-google.mdx b/apps/docs/content/guides/auth/social-login/auth-google.mdx index 520fb9a3a00e2..e73d62e6cf03c 100644 --- a/apps/docs/content/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-google.mdx @@ -540,26 +540,37 @@ Future _nativeGoogleSignIn() async { /// iOS Client ID that you registered with Google Cloud. const iosClientId = 'my-ios.apps.googleusercontent.com'; - final GoogleSignIn googleSignIn = GoogleSignIn( - clientId: iosClientId, + final scopes = ['email', 'profile']; + final googleSignIn = GoogleSignIn.instance; + + await googleSignIn.initialize( serverClientId: webClientId, + clientId: iosClientId, ); - final googleUser = await googleSignIn.signIn(); - final googleAuth = await googleUser!.authentication; - final accessToken = googleAuth.accessToken; - final idToken = googleAuth.idToken; - if (accessToken == null) { - throw 'No Access Token found.'; + final googleUser = await googleSignIn.attemptLightweightAuthentication(); + // or await googleSignIn.authenticate(); which will return a GoogleSignInAccount or throw an exception + + if (googleUser == null) { + throw AuthException('Failed to sign in with Google.'); } + + /// Authorization is required to obtain the access token with the appropriate scopes for Supabase authentication, + /// while also granting permission to access user information. + final authorization = + await googleUser.authorizationClient.authorizationForScopes(scopes) ?? + await googleUser.authorizationClient.authorizeScopes(scopes); + + final idToken = googleUser.authentication.idToken; + if (idToken == null) { - throw 'No ID Token found.'; + throw AuthException('No ID Token found.'); } await supabase.auth.signInWithIdToken( provider: OAuthProvider.google, idToken: idToken, - accessToken: accessToken, + accessToken: authorization.accessToken, ); } ...