diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a10f1ed..0444397 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -58,3 +58,6 @@ jobs:
- name: Build project
run: npm run build
+ env:
+ NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
+ NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
diff --git a/apps/web/package.json b/apps/web/package.json
index b6f5abb..1db4e2f 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,14 +17,17 @@
"clean": "npx rimraf .next next-env.d.ts node_modules"
},
"dependencies": {
+ "@supabase/auth-helpers-nextjs": "^0.10.0",
+ "@supabase/auth-helpers-react": "^0.5.0",
+ "@supabase/supabase-js": "^2.50.0",
"@tanstack/react-query": "^5.22.0",
"@tanstack/react-query-devtools": "^5.22.0",
"axios": "^1.6.7",
+ "easy-fixer-shared": "1.0.0",
"next": "15.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-hook-form": "^7.50.1",
- "easy-fixer-shared": "1.0.0"
+ "react-hook-form": "^7.50.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
diff --git a/apps/web/public/Googleicon.png b/apps/web/public/Googleicon.png
new file mode 100644
index 0000000..a753a79
Binary files /dev/null and b/apps/web/public/Googleicon.png differ
diff --git a/apps/web/public/logo.png b/apps/web/public/logo.png
new file mode 100644
index 0000000..e325009
Binary files /dev/null and b/apps/web/public/logo.png differ
diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx
deleted file mode 100644
index 3a5ca91..0000000
--- a/apps/web/src/app/(auth)/login/page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function LoginPage() {
- return (
-
- Login
- Login form will go here.
-
- );
-}
diff --git a/apps/web/src/app/(auth)/register/page.tsx b/apps/web/src/app/(auth)/register/page.tsx
deleted file mode 100644
index 9f400dd..0000000
--- a/apps/web/src/app/(auth)/register/page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function RegisterPage() {
- return (
-
- Register
- Registration form will go here.
-
- );
-}
diff --git a/apps/web/src/app/_landing/page.tsx b/apps/web/src/app/_landing/page.tsx
index 089560d..8eab7ce 100644
--- a/apps/web/src/app/_landing/page.tsx
+++ b/apps/web/src/app/_landing/page.tsx
@@ -12,14 +12,14 @@ export default function LandingPage() {
Login
diff --git a/apps/web/src/app/auth/callback/page.tsx b/apps/web/src/app/auth/callback/page.tsx
new file mode 100644
index 0000000..5db9562
--- /dev/null
+++ b/apps/web/src/app/auth/callback/page.tsx
@@ -0,0 +1,56 @@
+"use client";
+export const dynamic = "force-dynamic";
+
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import AuthRedirect from "@/components/AuthRedirect";
+import * as authServices from "@/lib/supabase/authServices";
+
+export default function OAuthCallback() {
+ const router = useRouter();
+ const [readyToRedirect, setReadyToRedirect] = useState(false);
+ const [hasRole, setHasRole] = useState(false);
+
+ useEffect(() => {
+ async function handleCallback() {
+ try {
+ const user = await authServices.getUser();
+
+ if (!user) {
+ router.replace("/auth/login");
+ return;
+ }
+
+ const userTypeFromLocal = localStorage.getItem("UserRole");
+
+ if (userTypeFromLocal) {
+ try {
+ await authServices.updateUserRole(userTypeFromLocal);
+ localStorage.removeItem("UserRole");
+ setHasRole(true);
+ } catch {
+ alert("Failed to update user role");
+ router.replace("/auth/login");
+ return;
+ }
+ } else {
+ const role = user.user_metadata?.role;
+ if (role) {
+ setHasRole(true);
+ } else {
+ router.replace("/auth/register");
+ }
+ }
+
+ setReadyToRedirect(true);
+ } catch {
+ router.replace("/auth/login");
+ }
+ }
+
+ handleCallback();
+ }, [router]);
+
+ if (!readyToRedirect) return
Loading...
;
+ if (hasRole) return
;
+}
diff --git a/apps/web/src/app/auth/forgot-password/page.tsx b/apps/web/src/app/auth/forgot-password/page.tsx
new file mode 100644
index 0000000..5c7633f
--- /dev/null
+++ b/apps/web/src/app/auth/forgot-password/page.tsx
@@ -0,0 +1,44 @@
+"use client";
+import { useState } from "react";
+import { useForgotPassword } from "./useForgotPassword";
+
+export default function ForgotPasswordPage() {
+ const [email, setEmail] = useState("");
+ const { sendResetLink, loading, error, success } = useForgotPassword();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ await sendResetLink(email);
+ };
+
+ return (
+
+
+
Forgot your password?
+
+ {error &&
{error}
}
+ {success && (
+
+ A password reset link has been sent to your email.
+
+ )}
+
+
+ );
+}
diff --git a/apps/web/src/app/auth/forgot-password/useForgotPassword.ts b/apps/web/src/app/auth/forgot-password/useForgotPassword.ts
new file mode 100644
index 0000000..1eda1da
--- /dev/null
+++ b/apps/web/src/app/auth/forgot-password/useForgotPassword.ts
@@ -0,0 +1,31 @@
+"use client";
+import { useState } from "react";
+import { sendPasswordResetLink } from "@/lib/supabase/authServices";
+
+export const useForgotPassword = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState
(null);
+ const [success, setSuccess] = useState(false);
+
+ const sendResetLink = async (email: string) => {
+ setLoading(true);
+ setError(null);
+ setSuccess(false);
+
+ try {
+ await sendPasswordResetLink(email);
+ setSuccess(true);
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ setError(err.message);
+ } else {
+ setError("Something went wrong");
+ }
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { sendResetLink, loading, error, success };
+};
diff --git a/apps/web/src/app/auth/login/GoogleLoginButton.tsx b/apps/web/src/app/auth/login/GoogleLoginButton.tsx
new file mode 100644
index 0000000..b7096b8
--- /dev/null
+++ b/apps/web/src/app/auth/login/GoogleLoginButton.tsx
@@ -0,0 +1,36 @@
+"use client";
+import { signInWithGoogle } from "@/lib/supabase/authServices";
+import Image from "next/image";
+interface GoogleLoginButtonProps {
+ role?: "client" | "provider";
+}
+
+export default function GoogleLoginButton({ role }: GoogleLoginButtonProps) {
+ const handleGoogleSignIn = async () => {
+ try {
+ await signInWithGoogle(role);
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ alert(err.message);
+ } else {
+ alert("Failed to initiate Google sign-in");
+ }
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/auth/login/LoginForm.tsx b/apps/web/src/app/auth/login/LoginForm.tsx
new file mode 100644
index 0000000..a02e3e6
--- /dev/null
+++ b/apps/web/src/app/auth/login/LoginForm.tsx
@@ -0,0 +1,85 @@
+"use client";
+import { useState } from "react";
+import { useLogin } from "./useLogin";
+import Link from "next/link";
+import GoogleLoginButton from "./GoogleLoginButton";
+import AuthRedirect from "@/components/AuthRedirect";
+import Logo from "../../../components/logo";
+import Back from "../../../components/Backbutton";
+export default function LoginForm() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [loginSuccess, setLoginSuccess] = useState(false);
+ const { login, loading, error } = useLogin();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ const result = await login(email, password);
+ if (result) {
+ setLoginSuccess(true);
+ }
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/apps/web/src/app/auth/login/page.tsx b/apps/web/src/app/auth/login/page.tsx
new file mode 100644
index 0000000..801182c
--- /dev/null
+++ b/apps/web/src/app/auth/login/page.tsx
@@ -0,0 +1,9 @@
+import LoginForm from "../login/LoginForm";
+
+export default function LoginPage() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/src/app/auth/login/useLogin.ts b/apps/web/src/app/auth/login/useLogin.ts
new file mode 100644
index 0000000..208af5d
--- /dev/null
+++ b/apps/web/src/app/auth/login/useLogin.ts
@@ -0,0 +1,28 @@
+"use client";
+import { useState } from "react";
+import { signInWithEmail } from "@/lib/supabase/authServices";
+
+export const useLogin = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const login = async (email: string, password: string) => {
+ setLoading(true);
+ setError(null);
+ try {
+ const data = await signInWithEmail(email, password);
+ return data;
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ setError(err.message);
+ } else {
+ setError("Something went wrong");
+ }
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { login, loading, error };
+};
diff --git a/apps/web/src/app/auth/register/RegisterForm.tsx b/apps/web/src/app/auth/register/RegisterForm.tsx
new file mode 100644
index 0000000..7b9e113
--- /dev/null
+++ b/apps/web/src/app/auth/register/RegisterForm.tsx
@@ -0,0 +1,124 @@
+"use client";
+import { useState } from "react";
+import Link from "next/link";
+import { useRegister } from "./useRegister";
+import GoogleLoginButton from "../login/GoogleLoginButton";
+
+import Logo from "../../../components/logo";
+import Back from "../../../components/Backbutton";
+
+export default function RegisterForm() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [confirm, setConfirm] = useState("");
+ const [role, setRole] = useState<"client" | "provider">("client");
+ const { register, loading, error } = useRegister();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (password !== confirm) {
+ alert("Passwords don't match");
+ return;
+ }
+ const result = await register(email, password, role);
+ if (result && !error) {
+ alert("Check your email for confirmation!");
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+ Create a new account
+
+
+
+ I am a
+
+
+
+
+
+
+
+ Already have an account?{" "}
+
+ Login
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/web/src/app/auth/register/page.tsx b/apps/web/src/app/auth/register/page.tsx
new file mode 100644
index 0000000..e446f9f
--- /dev/null
+++ b/apps/web/src/app/auth/register/page.tsx
@@ -0,0 +1,9 @@
+import RegisterForm from "./RegisterForm";
+
+export default function Registerpage() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/src/app/auth/register/useRegister.ts b/apps/web/src/app/auth/register/useRegister.ts
new file mode 100644
index 0000000..d41ba75
--- /dev/null
+++ b/apps/web/src/app/auth/register/useRegister.ts
@@ -0,0 +1,29 @@
+"use client";
+import { useState } from "react";
+import { signUpWithEmail } from "@/lib/supabase/authServices";
+
+export const useRegister = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const register = async (email: string, password: string, role: string) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const data = await signUpWithEmail(email, password, role);
+ return data;
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ setError(err.message);
+ } else {
+ setError("Something went wrong");
+ }
+ return null;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return { register, loading, error };
+};
diff --git a/apps/web/src/app/auth/reset-password/page.tsx b/apps/web/src/app/auth/reset-password/page.tsx
new file mode 100644
index 0000000..3f1dc09
--- /dev/null
+++ b/apps/web/src/app/auth/reset-password/page.tsx
@@ -0,0 +1,67 @@
+"use client";
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import {
+ restoreSessionFromUrl,
+ updatePassword,
+} from "@/lib/supabase/authServices";
+
+export default function ResetPasswordPage() {
+ const [newPassword, setNewPassword] = useState("");
+ const [message, setMessage] = useState("");
+ const router = useRouter();
+
+ useEffect(() => {
+ const hash = window.location.hash;
+ const params = new URLSearchParams(hash.substring(1));
+ const access_token = params.get("access_token");
+ const refresh_token = params.get("refresh_token");
+
+ if (access_token && refresh_token) {
+ restoreSessionFromUrl(access_token, refresh_token).catch((err) =>
+ setMessage("Failed to restore session: " + err.message),
+ );
+ }
+ }, []);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+
+ try {
+ await updatePassword(newPassword);
+ setMessage("Password changed successfully! Redirecting to login...");
+ setTimeout(() => router.push("/auth/login"), 3000);
+ } catch (err: unknown) {
+ if (err instanceof Error) {
+ setMessage(err.message);
+ } else {
+ setMessage("An unknown error occurred.");
+ }
+ }
+ };
+
+ return (
+
+
+
Reset Password
+
+ {message &&
{message}
}
+
+
+ );
+}
diff --git a/apps/web/src/app/dashboard/client/page.tsx b/apps/web/src/app/dashboard/client/page.tsx
new file mode 100644
index 0000000..2a3958f
--- /dev/null
+++ b/apps/web/src/app/dashboard/client/page.tsx
@@ -0,0 +1,7 @@
+export default function ClientDashboard() {
+ return (
+
+ Dashboard for Client (Coming Soon)
+
+ );
+}
diff --git a/apps/web/src/app/dashboard/provider/page.tsx b/apps/web/src/app/dashboard/provider/page.tsx
new file mode 100644
index 0000000..1d71750
--- /dev/null
+++ b/apps/web/src/app/dashboard/provider/page.tsx
@@ -0,0 +1,9 @@
+export default function ProviderDashboard() {
+ return (
+
+
+ Dashboard for Provider (Coming Soon)
+
+
+ );
+}
diff --git a/apps/web/src/components/AuthRedirect.tsx b/apps/web/src/components/AuthRedirect.tsx
new file mode 100644
index 0000000..7fabe52
--- /dev/null
+++ b/apps/web/src/components/AuthRedirect.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { useRouter } from "next/navigation";
+import { supabase } from "@/lib/supabase/client";
+
+export default function AuthRedirect() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ async function checkUserRole() {
+ const {
+ data: { user },
+ error,
+ } = await supabase.auth.getUser();
+
+ if (error || !user) {
+ router.push("/login");
+ return;
+ }
+
+ const role = user.user_metadata?.role;
+
+ if (!role) {
+ router.push("/login");
+ return;
+ }
+
+ if (role === "client") {
+ router.push("/dashboard/client");
+ } else if (role === "provider") {
+ router.push("/dashboard/provider");
+ } else {
+ router.push("/login");
+ }
+ }
+
+ checkUserRole().finally(() => setLoading(false));
+ }, [router]);
+
+ if (loading) return Loading...
;
+
+ return null;
+}
diff --git a/apps/web/src/components/Backbutton.tsx b/apps/web/src/components/Backbutton.tsx
new file mode 100644
index 0000000..5f77e75
--- /dev/null
+++ b/apps/web/src/components/Backbutton.tsx
@@ -0,0 +1,15 @@
+"use client";
+import { useRouter } from "next/navigation";
+
+export default function Back() {
+ const router = useRouter();
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/logo.tsx b/apps/web/src/components/logo.tsx
new file mode 100644
index 0000000..cac9fa0
--- /dev/null
+++ b/apps/web/src/components/logo.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import Link from "next/link";
+import Image from "next/image";
+
+type LogoProps = {
+ showText?: boolean;
+};
+
+export default function Logo({ showText = true }: LogoProps) {
+ return (
+
+
+ {showText && EasyFix}
+
+ );
+}
diff --git a/apps/web/src/lib/supabase/authServices.ts b/apps/web/src/lib/supabase/authServices.ts
new file mode 100644
index 0000000..f88f71c
--- /dev/null
+++ b/apps/web/src/lib/supabase/authServices.ts
@@ -0,0 +1,96 @@
+import { supabase } from "./client";
+
+export async function signInWithEmail(email: string, password: string) {
+ const { data, error } = await supabase.auth.signInWithPassword({
+ email,
+ password,
+ });
+
+ if (error) throw new Error(error.message);
+ return data;
+}
+
+export const signUpWithEmail = async (
+ email: string,
+ password: string,
+ role: string,
+) => {
+ const { data, error } = await supabase.auth.signUp({
+ email,
+ password,
+ options: {
+ data: { role },
+ },
+ });
+
+ if (error) throw new Error(error.message);
+ return data;
+};
+
+export const signInWithGoogle = async (role?: "client" | "provider") => {
+ if (role) {
+ localStorage.setItem("UserRole", role);
+ }
+
+ const { error } = await supabase.auth.signInWithOAuth({
+ provider: "google",
+ options: {
+ redirectTo: `${window.location.origin}/auth/callback`,
+ queryParams: {
+ prompt: "select_account",
+ },
+ },
+ });
+
+ if (error) throw new Error(error.message);
+};
+
+export async function signOut() {
+ const { error } = await supabase.auth.signOut();
+ if (error) throw new Error(error.message);
+}
+
+export const sendPasswordResetLink = async (email: string) => {
+ const { error } = await supabase.auth.resetPasswordForEmail(email, {
+ redirectTo: `${window.location.origin}/auth/reset-password`,
+ });
+
+ if (error) {
+ throw new Error(error.message);
+ }
+
+ return true;
+};
+
+export const restoreSessionFromUrl = async (
+ access_token: string,
+ refresh_token: string,
+) => {
+ const { error } = await supabase.auth.setSession({
+ access_token,
+ refresh_token,
+ });
+ if (error) {
+ throw new Error(error.message);
+ }
+};
+
+export const updatePassword = async (newPassword: string) => {
+ const { error } = await supabase.auth.updateUser({ password: newPassword });
+ if (error) {
+ throw new Error(error.message);
+ }
+};
+
+export async function getUser() {
+ const { data, error } = await supabase.auth.getUser();
+ if (error) throw new Error(error.message);
+ return data.user;
+}
+
+export async function updateUserRole(role: string) {
+ const { error } = await supabase.auth.updateUser({
+ data: { role },
+ });
+ if (error) throw new Error(error.message);
+}
diff --git a/apps/web/src/lib/supabase/client.ts b/apps/web/src/lib/supabase/client.ts
new file mode 100644
index 0000000..fd04da9
--- /dev/null
+++ b/apps/web/src/lib/supabase/client.ts
@@ -0,0 +1,6 @@
+import { createClient } from "@supabase/supabase-js";
+
+const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
+const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
+
+export const supabase = createClient(supabaseUrl, supabaseAnonKey);
diff --git a/package-lock.json b/package-lock.json
index 14e91fa..077a55b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -75,6 +75,9 @@
"apps/web": {
"version": "0.1.0",
"dependencies": {
+ "@supabase/auth-helpers-nextjs": "^0.10.0",
+ "@supabase/auth-helpers-react": "^0.5.0",
+ "@supabase/supabase-js": "^2.50.0",
"@tanstack/react-query": "^5.22.0",
"@tanstack/react-query-devtools": "^5.22.0",
"axios": "^1.6.7",
@@ -2849,6 +2852,139 @@
"hasInstallScript": true,
"license": "Apache-2.0"
},
+ "node_modules/@supabase/auth-helpers-nextjs": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-nextjs/-/auth-helpers-nextjs-0.10.0.tgz",
+ "integrity": "sha512-2dfOGsM4yZt0oS4TPiE7bD4vf7EVz7NRz/IJrV6vLg0GP7sMUx8wndv2euLGq4BjN9lUCpu6DG/uCC8j+ylwPg==",
+ "deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-helpers-shared": "0.7.0",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "peerDependencies": {
+ "@supabase/supabase-js": "^2.39.8"
+ }
+ },
+ "node_modules/@supabase/auth-helpers-react": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-react/-/auth-helpers-react-0.5.0.tgz",
+ "integrity": "sha512-5QSaV2CGuhDhd7RlQCoviVEAYsP7XnrFMReOcBazDvVmqSIyjKcDwhLhWvnrxMOq5qjOaA44MHo7wXqDiF0puQ==",
+ "deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.",
+ "license": "MIT",
+ "peerDependencies": {
+ "@supabase/supabase-js": "^2.39.8"
+ }
+ },
+ "node_modules/@supabase/auth-helpers-shared": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-helpers-shared/-/auth-helpers-shared-0.7.0.tgz",
+ "integrity": "sha512-FBFf2ei2R7QC+B/5wWkthMha8Ca2bWHAndN+syfuEUUfufv4mLcAgBCcgNg5nJR8L0gZfyuaxgubtOc9aW3Cpg==",
+ "deprecated": "This package is now deprecated - please use the @supabase/ssr package instead.",
+ "license": "MIT",
+ "dependencies": {
+ "jose": "^4.14.4"
+ },
+ "peerDependencies": {
+ "@supabase/supabase-js": "^2.39.8"
+ }
+ },
+ "node_modules/@supabase/auth-js": {
+ "version": "2.70.0",
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.70.0.tgz",
+ "integrity": "sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/functions-js": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz",
+ "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/node-fetch": {
+ "version": "2.6.15",
+ "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
+ "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ }
+ },
+ "node_modules/@supabase/node-fetch/node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/@supabase/node-fetch/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/@supabase/node-fetch/node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/@supabase/postgrest-js": {
+ "version": "1.19.4",
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz",
+ "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/realtime-js": {
+ "version": "2.11.10",
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.10.tgz",
+ "integrity": "sha512-SJKVa7EejnuyfImrbzx+HaD9i6T784khuw1zP+MBD7BmJYChegGxYigPzkKX8CK8nGuDntmeSD3fvriaH0EGZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.13",
+ "@types/phoenix": "^1.6.6",
+ "@types/ws": "^8.18.1",
+ "ws": "^8.18.2"
+ }
+ },
+ "node_modules/@supabase/storage-js": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz",
+ "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/node-fetch": "^2.6.14"
+ }
+ },
+ "node_modules/@supabase/supabase-js": {
+ "version": "2.50.0",
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.50.0.tgz",
+ "integrity": "sha512-M1Gd5tPaaghYZ9OjeO1iORRqbTWFEz/cF3pPubRnMPzA+A8SiUsXXWDP+DWsASZcjEcVEcVQIAF38i5wrijYOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@supabase/auth-js": "2.70.0",
+ "@supabase/functions-js": "2.4.4",
+ "@supabase/node-fetch": "2.6.15",
+ "@supabase/postgrest-js": "1.19.4",
+ "@supabase/realtime-js": "2.11.10",
+ "@supabase/storage-js": "2.7.1"
+ }
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -3770,7 +3906,6 @@
"version": "22.15.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.18.tgz",
"integrity": "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -3780,7 +3915,12 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/phoenix": {
+ "version": "1.6.6",
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
+ "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
"license": "MIT"
},
"node_modules/@types/qs": {
@@ -3913,6 +4053,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -9849,6 +9998,15 @@
"jiti": "lib/jiti-cli.mjs"
}
},
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -12874,6 +13032,12 @@
"node": ">= 18"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -15416,7 +15580,6 @@
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
diff --git a/package.json b/package.json
index 202ade8..be6cf9b 100644
--- a/package.json
+++ b/package.json
@@ -82,5 +82,6 @@
"workspaces": [
"apps/*",
"packages/*"
- ]
+ ],
+ "dependencies": {}
}