From 523ecb9d6e577a2ccf85b9c2d3d692f9bd916ac3 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Thu, 29 May 2025 21:43:04 +0800 Subject: [PATCH] start moving to tanstack router --- apps/studio/.gitignore | 4 +- .../data/permissions/permissions-query.ts | 6 +- apps/studio/index.html | 12 + apps/studio/lib/next-compat/link.tsx | 6 +- apps/studio/lib/next-compat/router.ts | 17 +- apps/studio/main.tsx | 22 + apps/studio/package.json | 20 +- apps/studio/react-router.config.ts | 6 - apps/studio/routeTree.gen.ts | 88 ++ apps/studio/routes/__root.tsx | 161 ++ apps/studio/routes/index.tsx | 13 + apps/studio/tsconfig.json | 11 +- apps/studio/vite.config.ts | 11 +- pnpm-lock.yaml | 1364 ++++++----------- 14 files changed, 834 insertions(+), 907 deletions(-) create mode 100644 apps/studio/index.html create mode 100644 apps/studio/main.tsx delete mode 100644 apps/studio/react-router.config.ts create mode 100644 apps/studio/routeTree.gen.ts create mode 100644 apps/studio/routes/__root.tsx create mode 100644 apps/studio/routes/index.tsx diff --git a/apps/studio/.gitignore b/apps/studio/.gitignore index a63d50148dd..50c9a32742e 100644 --- a/apps/studio/.gitignore +++ b/apps/studio/.gitignore @@ -8,11 +8,9 @@ # testing /coverage -# react-router -/.react-router/ - # production /build +/dist # misc .DS_Store diff --git a/apps/studio/data/permissions/permissions-query.ts b/apps/studio/data/permissions/permissions-query.ts index bd0966c9c55..b80218d6d06 100644 --- a/apps/studio/data/permissions/permissions-query.ts +++ b/apps/studio/data/permissions/permissions-query.ts @@ -1,8 +1,8 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query' +import { useIsLoggedIn } from 'common' import { get, handleError } from 'data/fetchers' import { IS_PLATFORM } from 'lib/constants' -import { useProfile } from 'lib/profile' import type { Permission, ResponseError } from 'types' import { permissionKeys } from './keys' @@ -23,14 +23,14 @@ export const usePermissionsQuery = ({ enabled = true, ...options }: UseQueryOptions = {}) => { - const { profile } = useProfile() + const isSignedIn = useIsLoggedIn() return useQuery( permissionKeys.list(), ({ signal }) => getPermissions(signal), { ...options, - enabled: IS_PLATFORM && enabled && profile !== undefined, + enabled: IS_PLATFORM && enabled && isSignedIn, staleTime: 30 * 60 * 1000, } ) diff --git a/apps/studio/index.html b/apps/studio/index.html new file mode 100644 index 00000000000..e78e23761e5 --- /dev/null +++ b/apps/studio/index.html @@ -0,0 +1,12 @@ + + + + + + Supabase + + +
+ + + diff --git a/apps/studio/lib/next-compat/link.tsx b/apps/studio/lib/next-compat/link.tsx index befa6e5fc59..f1efaa2e2cc 100644 --- a/apps/studio/lib/next-compat/link.tsx +++ b/apps/studio/lib/next-compat/link.tsx @@ -1,4 +1,4 @@ -import { Link as RemixLink } from 'react-router' +import { Link as TanstackRouterLink } from '@tanstack/react-router' export default function Link({ children, @@ -11,8 +11,8 @@ export default function Link({ passHref?: boolean }) { return ( - + {children} - + ) } diff --git a/apps/studio/lib/next-compat/router.ts b/apps/studio/lib/next-compat/router.ts index b0fff454c1c..aa0428ff240 100644 --- a/apps/studio/lib/next-compat/router.ts +++ b/apps/studio/lib/next-compat/router.ts @@ -1,26 +1,17 @@ -import { useLocation, useParams, useSearchParams, useNavigate } from 'react-router' +import { useLocation, useParams, useSearch, useNavigate } from '@tanstack/react-router' import { BASE_PATH } from 'lib/constants' import { NextRouter } from 'next/router' import { parse } from 'querystring' import { useMemo } from 'react' export function useRouter(): NextRouter { - const params = useParams() - const [searchParams] = useSearchParams() + const params = useParams({ strict: false }) + const searchParams = useSearch({ strict: false }) const location = useLocation() const navigate = useNavigate() const query = useMemo(() => { - let q: { [key: string]: string | string[] | undefined } = Object.fromEntries( - Array.from(new Set(searchParams.keys())).map((key) => { - const values = searchParams.getAll(key) - return [key, values.length > 1 ? values : values[0]] - }) - ) - - q = { ...q, ...params } - - return q + return { ...params, ...searchParams } }, [params, searchParams]) return { diff --git a/apps/studio/main.tsx b/apps/studio/main.tsx new file mode 100644 index 00000000000..7f2563f23f0 --- /dev/null +++ b/apps/studio/main.tsx @@ -0,0 +1,22 @@ +import { StrictMode } from 'react' +import ReactDOM from 'react-dom/client' +import { RouterProvider, createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const router = createRouter({ routeTree }) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +const rootElement = document.getElementById('root')! +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement) + root.render( + + + + ) +} diff --git a/apps/studio/package.json b/apps/studio/package.json index 4ea4fe72bd1..fdcfb436249 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -5,9 +5,9 @@ "type": "module", "scripts": { "preinstall": "npx only-allow pnpm", - "dev": "next dev --turbopack -p 8082", + "dev": "vite", "dev:secrets:pull": "AWS_PROFILE=supabase-dev node ../../scripts/getSecrets.js -n local/studio", - "build": "next build && ./../../scripts/upload-static-assets.sh", + "build": "vite build && ./../../scripts/upload-static-assets.sh", "start": "next start", "lint": "next lint", "clean": "rimraf node_modules tsconfig.tsbuildinfo .next .turbo", @@ -18,7 +18,7 @@ "test:ci": "vitest --run --coverage", "test:report": "open coverage/lcov-report/index.html", "deploy:staging": "VERCEL_ORG_ID=team_E6KJ1W561hMTjon1QSwOh0WO VERCEL_PROJECT_ID=QmcmhbiAtCMFTAHCuGgQscNbke4TzgWULECctNcKmxWCoT vercel --prod -A .vercel/staging.json", - "typecheck": "react-router typegen && tsc --noEmit", + "typecheck": "tsc --noEmit", "prettier:check": "prettier --check .", "prettier:write": "prettier --write .", "build:deno-types": "tsx scripts/deno-types.ts" @@ -39,9 +39,7 @@ "@heroicons/react": "^2.1.3", "@hookform/resolvers": "^3.1.1", "@monaco-editor/react": "^4.6.0", - "@next/bundle-analyzer": "15.3.1", "@number-flow/react": "^0.3.2", - "@sentry/nextjs": "^8.52.1", "@std/path": "npm:@jsr/std__path@^1.0.8", "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.5.0", @@ -53,9 +51,10 @@ "@supabase/supabase-js": "catalog:", "@tanstack/react-query": "4.35.7", "@tanstack/react-query-devtools": "4.35.7", + "@tanstack/react-router": "^1.114.27", + "@tanstack/react-router-devtools": "^1.120.11", "@uidotdev/usehooks": "^2.4.1", "@vercel/flags": "^2.6.0", - "@vitejs/plugin-react": "^4.3.4", "@zip.js/zip.js": "^2.7.29", "ai": "^3.4.33", "ai-commands": "workspace:*", @@ -133,10 +132,11 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { + "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@radix-ui/react-use-escape-keydown": "^1.0.3", - "@react-router/dev": "^7.1.1", "@supabase/postgres-meta": "^0.64.4", "@tailwindcss/container-queries": "^0.1.1", + "@tanstack/router-plugin": "^1.120.11", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.6.0", "@testing-library/react": "^16.0.0", @@ -164,6 +164,7 @@ "@types/sqlstring": "^2.3.0", "@types/uuid": "^8.3.4", "@types/zxcvbn": "^4.4.1", + "@vitejs/plugin-react": "^4.3.4", "@vitest/coverage-v8": "^3.0.9", "@vitest/ui": "^3.0.0", "api-types": "workspace:*", @@ -181,10 +182,13 @@ "prettier": "3.2.4", "raw-loader": "^4.0.2", "require-in-the-middle": "^7.5.2", + "rollup-plugin-node-polyfills": "^0.2.1", "tailwindcss": "^3.4.1", "tsx": "^4.19.3", "typescript": "~5.5.0", - "vite": "^6.2.6", + "vite": "^6.3.5", + "vite-plugin-circular-dependency": "^0.5.0", + "vite-plugin-cjs-interop": "^2.2.0", "vite-tsconfig-paths": "^4.3.2", "vitest": "^3.0.5" } diff --git a/apps/studio/react-router.config.ts b/apps/studio/react-router.config.ts deleted file mode 100644 index 0a37ed2171d..00000000000 --- a/apps/studio/react-router.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Config } from '@react-router/dev/config' - -export default { - basename: process.env.NEXT_PUBLIC_BASE_PATH ?? '/', - ssr: false, -} satisfies Config diff --git a/apps/studio/routeTree.gen.ts b/apps/studio/routeTree.gen.ts new file mode 100644 index 00000000000..5e682936bab --- /dev/null +++ b/apps/studio/routeTree.gen.ts @@ -0,0 +1,88 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as IndexImport } from './routes/index' + +// Create/Update Routes + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' + fileRoutesByTo: FileRoutesByTo + to: '/' + id: '__root__' | '/' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/" + ] + }, + "/": { + "filePath": "index.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/apps/studio/routes/__root.tsx b/apps/studio/routes/__root.tsx new file mode 100644 index 00000000000..38630bb17cb --- /dev/null +++ b/apps/studio/routes/__root.tsx @@ -0,0 +1,161 @@ +import { createRootRoute, Outlet } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' +import 'react-data-grid/lib/styles.css' +import 'styles/code.scss' +import 'styles/contextMenu.scss' +import 'styles/date-picker.scss' +import 'styles/editor.scss' +import 'styles/graphiql-base.scss' +import 'styles/grid.scss' +import 'styles/main.scss' +import 'styles/markdown-preview.scss' +import 'styles/monaco.scss' +import 'styles/react-data-grid-logs.scss' +import 'styles/reactflow.scss' +import 'styles/storage.scss' +import 'styles/stripe.scss' +import 'styles/toast.scss' +import 'styles/ui.scss' +import 'ui/build/css/themes/dark.css' +import 'ui/build/css/themes/light.css' + +import { loader } from '@monaco-editor/react' +import * as Sentry from '@sentry/nextjs' +import { Hydrate, QueryClientProvider } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' +import dayjs from 'dayjs' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import relativeTime from 'dayjs/plugin/relativeTime' +import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' +import Head from 'next/head' +import { NuqsAdapter } from 'nuqs/adapters/next/pages' +import { ErrorInfo } from 'react' +import { ErrorBoundary } from 'react-error-boundary' + +import { FeatureFlagProvider, ThemeProvider, useThemeSandbox } from 'common' +import MetaFaviconsPagesRouter from 'common/MetaFavicons/pages-router' +import { RouteValidationWrapper } from 'components/interfaces/App' +import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext' +import { StudioCommandMenu } from 'components/interfaces/App/CommandMenu' +import { FeaturePreviewContextProvider } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' +import FeaturePreviewModal from 'components/interfaces/App/FeaturePreview/FeaturePreviewModal' +import { MonacoThemeProvider } from 'components/interfaces/App/MonacoThemeProvider' +import { GenerateSql } from 'components/interfaces/SqlGenerator/SqlGenerator' +import { ErrorBoundaryState } from 'components/ui/ErrorBoundaryState' +import { useRootQueryClient } from 'data/query-client' +// import { customFont, sourceCodePro } from 'fonts' +import { AuthProvider } from 'lib/auth' +import { getFlags as getConfigCatFlags } from 'lib/configcat' +import { API_URL, BASE_PATH, IS_PLATFORM } from 'lib/constants' +import { ProfileProvider } from 'lib/profile' +import { Telemetry } from 'lib/telemetry' +import HCaptchaLoadedStore from 'stores/hcaptcha-loaded-store' +import { AppPropsWithLayout } from 'types' +import { SonnerToaster, TooltipProvider } from 'ui' +import { CommandProvider } from 'ui-patterns/CommandMenu' + +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(relativeTime) + +loader.config({ + // [Joshen] Attempt for offline support/bypass ISP issues is to store the assets required for monaco + // locally. We're however, only storing the assets which we need (based on what the network tab loads + // while using monaco). If we end up facing more effort trying to maintain this, probably to either + // use cloudflare or find some way to pull all the files from a CDN via a CLI, rather than tracking individual files + // The alternative was to import * as monaco from 'monaco-editor' but i couldn't get it working + paths: { + vs: IS_PLATFORM + ? 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' + : `${BASE_PATH}/monaco-editor`, + }, +}) + +// [Joshen TODO] Once we settle on the new nav layout - we'll need a lot of clean up in terms of our layout components +// a lot of them are unnecessary and introduce way too many cluttered CSS especially with the height styles that make +// debugging way too difficult. Ideal scenario is we just have one AppLayout to control the height and scroll areas of +// the dashboard, all other layout components should not be doing that + +function CustomApp() { + const queryClient = useRootQueryClient() + + const errorBoundaryHandler = (error: Error, info: ErrorInfo) => { + Sentry.withScope(function (scope) { + scope.setTag('globalErrorBoundary', true) + Sentry.captureException(error) + }) + + console.error(error.stack) + } + + useThemeSandbox() + + const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test' + + return ( + + + + + + + + Supabase + + {/* [Alaister]: This has to be an inline style tag here and not a separate component due to next/font */} + {/*