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?

+
+ setEmail(e.target.value)} + required + className="w-full p-2 border rounded mb-4" + /> + +
+ {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 ( + <> + + +
+
+ {loginSuccess && } + +

+ Login to your account +

+ + setEmail(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400 bg-white text-sm" + /> + setPassword(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400 bg-white text-sm" + /> + + {error && ( +

{error}

+ )} + + + +
+ + Forgot your password? + +
+ +
+
+ or +
+
+ + + + +
+ + ); +} 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 + + +
+ +
+ setEmail(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400 bg-white text-sm" + /> + setPassword(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400 bg-white text-sm" + /> + setConfirm(e.target.value)} + required + className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-400 bg-white text-sm" + /> + + {error && ( +

{error}

+ )} + + +
+ +
+ Already have an account?{" "} + + Login + +
+ +
+
+ or +
+
+ + + +
+ + ); +} 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

+
+ setNewPassword(e.target.value)} + required + className="w-full p-2 border rounded mb-4" + /> + +
+ {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 ( + + EasyFix Logo + {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": {} }