diff --git a/packages/client/app/components/auth/auth-form.tsx b/packages/client/app/components/auth/auth-form.tsx
index 1b0a17e..bc8c058 100644
--- a/packages/client/app/components/auth/auth-form.tsx
+++ b/packages/client/app/components/auth/auth-form.tsx
@@ -58,9 +58,9 @@ export const AuthForm = ({ error }: AuthFormProps) => {
id="password"
name="password"
type="password"
- placeholder="••••••••••"
+ placeholder="········"
required
- className="h-11 text-base"
+ className="h-11 text-base placeholder:font-extrabold"
/>
diff --git a/packages/client/app/components/auth/sign-up-form.tsx b/packages/client/app/components/auth/sign-up-form.tsx
index c8f087a..1c7ab90 100644
--- a/packages/client/app/components/auth/sign-up-form.tsx
+++ b/packages/client/app/components/auth/sign-up-form.tsx
@@ -55,9 +55,9 @@ export const SignUpForm = ({ error, success }: SignUpFormProps) => {
id="password"
name="password"
type="password"
- placeholder="••••••••"
+ placeholder="········"
required
- className="h-11 text-base"
+ className="h-11 text-base placeholder:font-extrabold"
/>
diff --git a/packages/client/app/components/marketing/testimonial.tsx b/packages/client/app/components/marketing/testimonial.tsx
index e83a854..56a9b93 100644
--- a/packages/client/app/components/marketing/testimonial.tsx
+++ b/packages/client/app/components/marketing/testimonial.tsx
@@ -1,18 +1,52 @@
+import { useState, useEffect } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+
export const Testimonial = () => {
+ const testimonials = [
+ "Data-River has completely transformed how my team and I handle automation. The visual editor makes complex workflows accessible, even for those with no coding experience. It's intuitive, powerful, and simply brilliant!",
+ "The modular design of Data-River is perfect for our growing team. We can build, share, and customize blocks, which saves us hours on repetitive tasks. Plus, our non-tech members love the easy drag-and-drop setup.",
+ "As an educator, Data-River is a game changer. Its intuitive, visual approach makes it easy to introduce programming concepts to students, and the platform’s flexibility allows them to explore more advanced logic when they’re ready.",
+ ];
+
+ const [testimonial, setTestimonial] = useState(testimonials[0]);
+
+ useEffect(() => {
+ const randomIndex = Math.floor(Math.random() * testimonials.length);
+ setTestimonial(testimonials[randomIndex]);
+
+ const interval = setInterval(() => {
+ setTestimonial((current) => {
+ const currentIndex = testimonials.indexOf(current);
+ const nextIndex = (currentIndex + 1) % testimonials.length;
+ return testimonials[nextIndex];
+ });
+ }, 8000);
+
+ return () => clearInterval(interval);
+ }, []);
+
return (
-
-
- "I just learned about Data River and I'm in love! It's an open
- source Firebase alternative with real-time database changes and
- simple UI for database interaction."
-
-
-
+
+
+
+ "{testimonial}"
+
+
+
);
diff --git a/packages/client/app/routes/_app.profile.$userName.tsx b/packages/client/app/routes/_app.profile.$userName.tsx
index 83e2788..2472194 100644
--- a/packages/client/app/routes/_app.profile.$userName.tsx
+++ b/packages/client/app/routes/_app.profile.$userName.tsx
@@ -1,7 +1,7 @@
import { useLoaderData } from "@remix-run/react";
import { type LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { getSession } from "~/utils/session.server";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import type { Database } from "~/types/supabase";
import { ProfileHeader } from "~/components/profile/profile-header";
import { AchievementsCard } from "~/components/profile/achievements-card";
@@ -18,6 +18,8 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
return redirect("/profile");
}
+ const { supabase } = await createClient(request);
+
const { data: profile, error } = await supabase
.from("profiles")
.select("*")
diff --git a/packages/client/app/routes/_app.settings.profile.avatar.tsx b/packages/client/app/routes/_app.settings.profile.avatar.tsx
index 57e43f4..5020862 100644
--- a/packages/client/app/routes/_app.settings.profile.avatar.tsx
+++ b/packages/client/app/routes/_app.settings.profile.avatar.tsx
@@ -4,7 +4,7 @@ import {
unstable_parseMultipartFormData,
} from "@remix-run/node";
import { getSession } from "~/utils/session.server";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import { uploadHandler } from "~/utils/upload.server";
import { deleteStorageFile } from "~/utils/storage.server";
@@ -13,7 +13,6 @@ const AVATARS_BUCKET = "avatars";
async function handleAvatarUpload(
request: Request,
userId: string,
- accessToken: string,
oldAvatarUrl: string | null,
) {
let newAvatarUrl: string | null = null;
@@ -21,12 +20,13 @@ async function handleAvatarUpload(
try {
const formData = await unstable_parseMultipartFormData(
request,
- uploadHandler(accessToken),
+ uploadHandler(request),
);
newAvatarUrl = formData.get("avatar") as string;
// Update profile with new avatar URL
+ const { supabase } = await createClient(request);
const { error: updateError } = await supabase
.from("profiles")
.update({
@@ -40,13 +40,13 @@ async function handleAvatarUpload(
}
// Delete old avatar if it exists
- await deleteStorageFile(oldAvatarUrl, accessToken, AVATARS_BUCKET);
+ await deleteStorageFile(request, oldAvatarUrl, AVATARS_BUCKET);
return json({ success: true });
} catch (error) {
// If we failed after uploading the new avatar, clean it up
if (newAvatarUrl) {
- await deleteStorageFile(newAvatarUrl, accessToken, AVATARS_BUCKET);
+ await deleteStorageFile(request, newAvatarUrl, AVATARS_BUCKET);
}
return json(
@@ -61,11 +61,12 @@ async function handleAvatarUpload(
}
async function handleAvatarRemoval(
+ request: Request,
userId: string,
oldAvatarUrl: string | null,
- accessToken: string,
) {
try {
+ const { supabase } = await createClient(request);
const { error } = await supabase
.from("profiles")
.update({ avatar_url: null, updated_at: new Date().toISOString() })
@@ -76,7 +77,7 @@ async function handleAvatarRemoval(
}
// Delete the old avatar file
- await deleteStorageFile(oldAvatarUrl, accessToken, AVATARS_BUCKET);
+ await deleteStorageFile(request, oldAvatarUrl, AVATARS_BUCKET);
return json({ success: true });
} catch (error) {
@@ -98,6 +99,7 @@ export async function action({ request }: ActionFunctionArgs) {
const userId = session.get("user_id") as string;
// Fetch current profile to get the old avatar URL
+ const { supabase } = await createClient(request);
const { data: profile, error: profileError } = await supabase
.from("profiles")
.select("avatar_url")
@@ -120,10 +122,10 @@ export async function action({ request }: ActionFunctionArgs) {
) {
const formData = await request.formData();
if (formData.get("_action") === "remove") {
- return handleAvatarRemoval(userId, oldAvatarUrl, accessToken);
+ return handleAvatarRemoval(request, userId, oldAvatarUrl);
}
}
// Handle avatar upload
- return handleAvatarUpload(request, userId, accessToken, oldAvatarUrl);
+ return handleAvatarUpload(request, userId, oldAvatarUrl);
}
diff --git a/packages/client/app/routes/_app.settings.profile.tsx b/packages/client/app/routes/_app.settings.profile.tsx
index ebf7a42..a4fa269 100644
--- a/packages/client/app/routes/_app.settings.profile.tsx
+++ b/packages/client/app/routes/_app.settings.profile.tsx
@@ -6,7 +6,7 @@ import {
redirect,
} from "@remix-run/node";
import { getSession } from "~/utils/session.server";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import {
Card,
CardContent,
@@ -31,6 +31,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
const session = await getSession(request);
const userId = session.get("user_id") as string;
+ const { supabase } = await createClient(request);
const { data: profile, error } = await supabase
.from("profiles")
.select("*")
@@ -76,6 +77,7 @@ export async function action({ request }: ActionFunctionArgs) {
);
}
+ const { supabase } = await createClient(request);
const { error } = await supabase
.from("profiles")
.update({
diff --git a/packages/client/app/routes/_app.tsx b/packages/client/app/routes/_app.tsx
index 89e0b29..2d4af7a 100644
--- a/packages/client/app/routes/_app.tsx
+++ b/packages/client/app/routes/_app.tsx
@@ -1,10 +1,11 @@
import { Outlet, useLoaderData } from "@remix-run/react";
import { type LoaderFunctionArgs, json, redirect } from "@remix-run/node";
import { getSession } from "~/utils/session.server";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import type { Database } from "~/types/supabase";
import { Navbar } from "~/components/layout/navbar";
import { CookieConsent } from "~/components/layout/cookie-consent";
+import { isRedirectResponse } from "@remix-run/react/dist/data";
type LoaderData = {
profile: Pick<
@@ -14,27 +15,21 @@ type LoaderData = {
};
export async function loader({ request }: LoaderFunctionArgs) {
- const session = await getSession(request.headers.get("Cookie"));
-
- if (!session.has("access_token")) {
- // TODO: Remove this after testing
- return json({
- profile: {
- id: "test",
- display_name: "test",
- avatar_url: "test",
- username: "test",
- },
- });
- // return redirect("/sign-in");
- }
+ const { supabase } = await createClient(request);
+
+ let {
+ data: { user },
+ } = await supabase.auth.getUser();
- const userId = session.get("user_id") as string;
+ if (!user) {
+ console.log("redirecting to sign-in");
+ return redirect("/sign-in");
+ }
const { data: profile, error } = await supabase
.from("profiles")
.select("*")
- .eq("id", userId)
+ .eq("id", user.id)
.single();
if (error || !profile) {
diff --git a/packages/client/app/routes/auth.callback.tsx b/packages/client/app/routes/auth.callback.tsx
index 21d906b..a4c5fe1 100644
--- a/packages/client/app/routes/auth.callback.tsx
+++ b/packages/client/app/routes/auth.callback.tsx
@@ -4,7 +4,7 @@ import {
type ActionFunctionArgs,
json,
} from "@remix-run/node";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import { getSession, commitSession } from "~/utils/session.server";
export async function loader({ request }: LoaderFunctionArgs) {
@@ -13,12 +13,21 @@ export async function loader({ request }: LoaderFunctionArgs) {
const next = url.searchParams.get("next") || "/editor";
if (code) {
+ const { supabase } = await createClient(request);
+
const { data, error } = await supabase.auth.exchangeCodeForSession(code);
+ if (error) {
+ console.error("Error exchanging code for session", error);
+ return redirect(`/sign-in?error=${error.message}`);
+ }
+
if (!error && data.session) {
const session = await getSession(request.headers.get("Cookie"));
- session.set("access_token", data.session.access_token);
session.set("user_id", data.session.user.id);
+ session.set("expires_at", data.session.expires_at);
+ session.set("access_token", data.session.access_token);
+ session.set("refresh_token", data.session.refresh_token);
return redirect(next, {
headers: {
@@ -35,6 +44,8 @@ export async function action({ request }: ActionFunctionArgs) {
const { access_token } = await request.json();
if (access_token) {
+ const { supabase } = await createClient(request);
+
const {
data: { user },
error,
diff --git a/packages/client/app/routes/auth.github.tsx b/packages/client/app/routes/auth.github.tsx
index df5c18e..94c95b4 100644
--- a/packages/client/app/routes/auth.github.tsx
+++ b/packages/client/app/routes/auth.github.tsx
@@ -1,8 +1,10 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
export async function action({ request }: ActionFunctionArgs) {
+ const { supabase, headers } = await createClient(request);
+
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "github",
options: {
@@ -12,5 +14,7 @@ export async function action({ request }: ActionFunctionArgs) {
if (error) throw error;
- return redirect(data.url);
+ return redirect(data.url, {
+ headers,
+ });
}
diff --git a/packages/client/app/routes/forgot-password.tsx b/packages/client/app/routes/forgot-password.tsx
index a97859b..418819d 100644
--- a/packages/client/app/routes/forgot-password.tsx
+++ b/packages/client/app/routes/forgot-password.tsx
@@ -7,7 +7,7 @@ import { json, redirect } from "@remix-run/node";
import { Card, CardHeader, CardContent } from "@data-river/shared/ui";
import { AuthLayout } from "~/components/layout/auth-layout";
import { ForgotPasswordForm } from "~/components/auth/forgot-password-form";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import { getSession } from "~/utils/session.server";
import { useActionData } from "@remix-run/react";
@@ -38,6 +38,7 @@ export async function action({ request }: ActionFunctionArgs) {
const email = formData.get("email") as string;
try {
+ const { supabase } = await createClient(request);
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${new URL(request.url).origin}/reset-password`,
});
diff --git a/packages/client/app/routes/sign-in.tsx b/packages/client/app/routes/sign-in.tsx
index bba324c..b5b2ce4 100644
--- a/packages/client/app/routes/sign-in.tsx
+++ b/packages/client/app/routes/sign-in.tsx
@@ -7,7 +7,7 @@ import { json, redirect } from "@remix-run/node";
import { Card, CardHeader, CardContent } from "@data-river/shared/ui";
import { AuthLayout } from "~/components/layout/auth-layout";
import { AuthForm } from "~/components/auth/auth-form";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import { getSession, commitSession } from "~/utils/session.server";
import { useActionData } from "@remix-run/react";
import { useEffect } from "react";
@@ -21,9 +21,13 @@ export const meta: MetaFunction = () => {
};
export async function loader({ request }: LoaderFunctionArgs) {
- const session = await getSession(request.headers.get("Cookie"));
+ const { supabase } = await createClient(request, true);
- if (session.has("access_token")) {
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+
+ if (user) {
return redirect("/editor");
}
@@ -36,6 +40,7 @@ export async function action({ request }: ActionFunctionArgs) {
const password = formData.get("password") as string;
try {
+ const { supabase } = await createClient(request, true);
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
@@ -46,6 +51,8 @@ export async function action({ request }: ActionFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));
session.set("access_token", data.session.access_token);
session.set("user_id", data.user.id);
+ session.set("refresh_token", data.session.refresh_token);
+ session.set("expires_at", data.session.expires_at);
return redirect("/editor", {
headers: {
diff --git a/packages/client/app/routes/sign-up.tsx b/packages/client/app/routes/sign-up.tsx
index a8bbe19..e50cabc 100644
--- a/packages/client/app/routes/sign-up.tsx
+++ b/packages/client/app/routes/sign-up.tsx
@@ -14,7 +14,7 @@ import {
} from "@data-river/shared/ui";
import { AuthLayout } from "~/components/layout/auth-layout";
import { SignUpForm } from "~/components/auth/sign-up-form";
-import { supabase } from "~/utils/supabase.server";
+import { createClient } from "~/utils/supabase.server";
import { getSession, commitSession } from "~/utils/session.server";
import { useActionData } from "@remix-run/react";
import { useState } from "react";
@@ -41,6 +41,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
const accessToken = session.get("access_token");
if (accessToken) {
+ const { supabase } = await createClient(request);
const {
data: { user },
} = await supabase.auth.getUser(accessToken);
@@ -73,6 +74,8 @@ export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const username = formData.get("username") as string;
+ const { supabase } = await createClient(request);
+
// Handle username selection
if (username) {
try {
@@ -185,7 +188,6 @@ export async function action({ request }: ActionFunctionArgs) {
const SignUpPage = () => {
const actionData = useActionData();
- const navigate = useNavigate();
// Show username selection form
if (actionData?.step === "username") {
diff --git a/packages/client/app/utils/storage.server.ts b/packages/client/app/utils/storage.server.ts
index 7c2ad37..ded7e81 100644
--- a/packages/client/app/utils/storage.server.ts
+++ b/packages/client/app/utils/storage.server.ts
@@ -1,14 +1,14 @@
-import { createServerClient } from "./supabase.server";
+import { createClient } from "./supabase.server";
export async function deleteStorageFile(
+ request: Request,
path: string | null,
- accessToken: string,
bucket: string,
) {
if (!path) return;
try {
- const supabase = createServerClient(accessToken);
+ const { supabase } = await createClient(request);
const { error } = await supabase.storage
.from(bucket)
.remove([getFileNameFromUrl(path)]);
diff --git a/packages/client/app/utils/supabase.server.ts b/packages/client/app/utils/supabase.server.ts
index 900523b..a864f9f 100644
--- a/packages/client/app/utils/supabase.server.ts
+++ b/packages/client/app/utils/supabase.server.ts
@@ -1,14 +1,49 @@
-import { createClient } from "@supabase/supabase-js";
+import {
+ createServerClient,
+ parseCookieHeader,
+ serializeCookieHeader,
+} from "@supabase/ssr";
import { type Database } from "~/types/supabase";
+import { getSession } from "./session.server";
if (!process.env.SUPABASE_URL) throw new Error("Missing SUPABASE_URL");
if (!process.env.SUPABASE_ANON_KEY)
throw new Error("Missing SUPABASE_ANON_KEY");
-export const supabase = createClient(
- process.env.SUPABASE_URL,
- process.env.SUPABASE_ANON_KEY,
-);
+export async function createClient(request: Request, skipRefresh = false) {
+ const headers = new Headers();
-export const createServerClient = (accessToken: string) =>
- createClient(process.env.SUPABASE_URL!, accessToken);
+ const supabase = await createServerClient(
+ process.env.SUPABASE_URL!,
+ process.env.SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return parseCookieHeader(request.headers.get("Cookie") ?? "");
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value, options }) =>
+ headers.append(
+ "Set-Cookie",
+ serializeCookieHeader(name, value, options),
+ ),
+ );
+ },
+ },
+ },
+ );
+
+ let errorRefreshingSession: Error | null = null;
+ if (!skipRefresh) {
+ const session = await getSession(request.headers.get("Cookie"));
+
+ const { error } = await supabase.auth.setSession({
+ access_token: session.get("access_token") as string,
+ refresh_token: session.get("refresh_token") as string,
+ });
+
+ if (error) errorRefreshingSession = error;
+ }
+
+ return { supabase, headers, errorRefreshingSession };
+}
diff --git a/packages/client/app/utils/upload.server.ts b/packages/client/app/utils/upload.server.ts
index 67decf7..2ee8425 100644
--- a/packages/client/app/utils/upload.server.ts
+++ b/packages/client/app/utils/upload.server.ts
@@ -1,5 +1,5 @@
import type { UploadHandler } from "@remix-run/node";
-import { createServerClient } from "./supabase.server";
+import { createClient } from "./supabase.server";
import { v4 as uuidv4 } from "uuid";
import sharp from "sharp";
@@ -41,18 +41,14 @@ async function compressImage(
}
export const uploadHandler =
- (accessToken: string): UploadHandler =>
+ (request: Request): UploadHandler =>
async ({ name, contentType, data }) => {
if (name !== "avatar") {
return undefined;
}
- if (!accessToken) {
- throw new Error("Unauthorized");
- }
-
// Create an authenticated Supabase client
- const supabase = createServerClient(accessToken);
+ const { supabase } = await createClient(request);
// Validate content type
if (!Object.keys(ALLOWED_TYPES).includes(contentType)) {
@@ -103,8 +99,6 @@ export const uploadHandler =
} catch (cleanupError) {
console.error("Failed to clean up file after error:", cleanupError);
}
-
- console.error("Upload error:", error);
throw new Error("Failed to upload file");
}
};
diff --git a/packages/client/package.json b/packages/client/package.json
index 0a363fd..b8bad63 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -53,6 +53,7 @@
"@remix-run/router": "^1.20.0",
"@remix-run/serve": "^2.13.1",
"@remix-run/server-runtime": "^2.13.1",
+ "@supabase/ssr": "^0.5.1",
"@supabase/supabase-js": "^2.45.4",
"@types/set-cookie-parser": "^2.4.10",
"@web3-storage/multipart-parser": "^1.0.0",
diff --git a/packages/client/supabase/migrations/2024110700000_avatar_bucket_null_owner.sql b/packages/client/supabase/migrations/2024110700000_avatar_bucket_null_owner.sql
new file mode 100644
index 0000000..6a150f4
--- /dev/null
+++ b/packages/client/supabase/migrations/2024110700000_avatar_bucket_null_owner.sql
@@ -0,0 +1,36 @@
+begin; -- Start transaction
+
+-- Drop existing policies
+DROP POLICY IF EXISTS "Allow users to delete their own avatar" ON storage.objects;
+DROP POLICY IF EXISTS "Allow users to insert their own avatar" ON storage.objects;
+DROP POLICY IF EXISTS "Allow users to update their own avatar" ON storage.objects;
+
+-- Create updated policies that allow actions when owner is null
+CREATE POLICY "Allow users to delete their own avatar or null owner"
+ON storage.objects
+FOR DELETE
+TO authenticated
+USING (
+ bucket_id = 'avatars' AND
+ (owner IS NULL OR auth.uid() = owner)
+);
+
+CREATE POLICY "Allow users to insert their own avatar or null owner"
+ON storage.objects
+FOR INSERT
+TO authenticated
+WITH CHECK (
+ bucket_id = 'avatars' AND
+ (owner IS NULL OR owner = auth.uid())
+);
+
+CREATE POLICY "Allow users to update their own avatar or null owner"
+ON storage.objects
+FOR UPDATE
+TO authenticated
+WITH CHECK (
+ bucket_id = 'avatars' AND
+ (owner IS NULL OR owner = auth.uid())
+);
+
+commit; -- End transaction
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index be9b059..cec3a3a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -214,6 +214,9 @@ importers:
'@remix-run/server-runtime':
specifier: ^2.13.1
version: 2.13.1(typescript@5.6.3)
+ '@supabase/ssr':
+ specifier: ^0.5.1
+ version: 0.5.1(@supabase/supabase-js@2.45.4)
'@supabase/supabase-js':
specifier: ^2.45.4
version: 2.45.4
@@ -2990,6 +2993,11 @@ packages:
'@supabase/realtime-js@2.10.2':
resolution: {integrity: sha512-qyCQaNg90HmJstsvr2aJNxK2zgoKh9ZZA8oqb7UT2LCh3mj9zpa3Iwu167AuyNxsxrUE8eEJ2yH6wLCij4EApA==}
+ '@supabase/ssr@0.5.1':
+ resolution: {integrity: sha512-+G94H/GZG0nErZ3FQV9yJmsC5Rj7dmcfCAwOt37hxeR1La+QTl8cE9whzYwPUrTJjMLGNXoO+1BMvVxwBAbz4g==}
+ peerDependencies:
+ '@supabase/supabase-js': ^2.43.4
+
'@supabase/storage-js@2.7.0':
resolution: {integrity: sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig==}
@@ -10532,6 +10540,11 @@ snapshots:
- bufferutil
- utf-8-validate
+ '@supabase/ssr@0.5.1(@supabase/supabase-js@2.45.4)':
+ dependencies:
+ '@supabase/supabase-js': 2.45.4
+ cookie: 0.6.0
+
'@supabase/storage-js@2.7.0':
dependencies:
'@supabase/node-fetch': 2.6.15
@@ -12216,7 +12229,7 @@ snapshots:
debug: 4.3.7
enhanced-resolve: 5.17.1
eslint: 9.12.0(jiti@1.21.6)
- eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6))
+ eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6))
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
@@ -12229,7 +12242,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.11.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6)):
+ eslint-module-utils@2.11.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -12239,7 +12252,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6)):
+ eslint-module-utils@2.12.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6)):
dependencies:
debug: 3.2.7
optionalDependencies:
@@ -12261,7 +12274,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.12.0(jiti@1.21.6)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@9.12.0(jiti@1.21.6)))(eslint@9.12.0(jiti@1.21.6))
+ eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.10.0(eslint@9.12.0(jiti@1.21.6))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.12.0(jiti@1.21.6))
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3