From 6366e23ba378bd9b2cee786b8d863505a143aade Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 22:40:17 +0000 Subject: [PATCH] Changes --- .env | 3 + package-lock.json | 114 ++++++++++- package.json | 1 + src/components/AccountMenu.tsx | 92 +++++++++ src/components/AuthDialog.tsx | 170 ++++++++++++++++ src/components/Header.tsx | 18 +- src/hooks/useAuth.ts | 62 ++++++ src/integrations/supabase/client.ts | 17 ++ src/integrations/supabase/types.ts | 184 ++++++++++++++++++ supabase/config.toml | 1 + ...5_bfbb8689-adb7-4368-9b59-7600297cb8d8.sql | 62 ++++++ 11 files changed, 713 insertions(+), 11 deletions(-) create mode 100644 .env create mode 100644 src/components/AccountMenu.tsx create mode 100644 src/components/AuthDialog.tsx create mode 100644 src/hooks/useAuth.ts create mode 100644 src/integrations/supabase/client.ts create mode 100644 src/integrations/supabase/types.ts create mode 100644 supabase/config.toml create mode 100644 supabase/migrations/20260127223925_bfbb8689-adb7-4368-9b59-7600297cb8d8.sql diff --git a/.env b/.env new file mode 100644 index 0000000..01e0502 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +VITE_SUPABASE_PROJECT_ID="teeojvlqulhghditoakc" +VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRlZW9qdmxxdWxoZ2hkaXRvYWtjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njk1NTIzMDksImV4cCI6MjA4NTEyODMwOX0.3F29aAE33-RCoitlcTrka0F6RVlpeuagTqukhWzZ-R4" +VITE_SUPABASE_URL="https://teeojvlqulhghditoakc.supabase.co" diff --git a/package-lock.json b/package-lock.json index 4612b84..2332250 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@supabase/supabase-js": "^2.93.1", "@tanstack/react-query": "^5.83.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -2518,6 +2519,86 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.93.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.93.1.tgz", + "integrity": "sha512-pC0Ek4xk4z6q7A/3+UuZ/eYgfFUUQTg3DhapzrAgJnFGDJDFDyGCj6v9nIz8+3jfLqSZ3QKGe6AoEodYjShghg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.93.1", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.93.1.tgz", + "integrity": "sha512-Ott2IcIXHGupaC0nX9WNEiJAX4OdlGRu9upkkURaQHbaLdz9JuCcHxlwTERgtgjMpikbIWHfMM1M9QTQFYABiA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.93.1", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.93.1.tgz", + "integrity": "sha512-uRKKQJBDnfi6XFNFPNMh9+u3HT2PCgp065PcMPmG7e0xGuqvLtN89QxO2/SZcGbw2y1+mNBz0yUs5KmyNqF2fA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.93.1", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.93.1.tgz", + "integrity": "sha512-2WaP/KVHPlQDjWM6qe4wOZz6zSRGaXw1lfXf4thbfvk3C3zPPKqXRyspyYnk3IhphyxSsJ2hQ/cXNOz48008tg==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.93.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.93.1.tgz", + "integrity": "sha512-3KVwd4S1i1BVPL6KIywe5rnruNQXSkLyvrdiJmwnqwbCcDujQumARdGWBPesqCjOPKEU2M9ORWKAsn+2iLzquA==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.93.1", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.93.1.tgz", + "integrity": "sha512-FJTgS5s0xEgRQ3u7gMuzGObwf3jA4O5Ki/DgCDXx94w1pihLM4/WG3XFa4BaCJYfuzLxLcv6zPPA5tDvBUjAUg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.93.1", + "@supabase/functions-js": "2.93.1", + "@supabase/postgrest-js": "2.93.1", + "@supabase/realtime-js": "2.93.1", + "@supabase/storage-js": "2.93.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/core": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", @@ -2956,12 +3037,17 @@ "version": "22.16.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.16.5.tgz", "integrity": "sha512-bJFoMATwIGaxxx8VJPeM8TonI8t579oRvgAuT8zFugJsJZgzqv0Fu8Mhp68iecjzG7cnN3mO2dJQ5uUM2EFrgQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -2990,6 +3076,15 @@ "@types/react": "^18.0.0" } }, + "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/@typescript-eslint/eslint-plugin": { "version": "8.38.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", @@ -5130,6 +5225,15 @@ "node": ">= 6" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7529,9 +7633,9 @@ "license": "Apache-2.0" }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/type-check": { @@ -7589,7 +7693,6 @@ "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/universalify": { @@ -8120,7 +8223,6 @@ "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 485c108..3873b8b 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", + "@supabase/supabase-js": "^2.93.1", "@tanstack/react-query": "^5.83.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/components/AccountMenu.tsx b/src/components/AccountMenu.tsx new file mode 100644 index 0000000..d65341e --- /dev/null +++ b/src/components/AccountMenu.tsx @@ -0,0 +1,92 @@ +import { useState } from "react"; +import { User, LogIn, LogOut, ChevronDown } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Button } from "@/components/ui/button"; +import { useAuth } from "@/hooks/useAuth"; +import AuthDialog from "./AuthDialog"; + +const AccountMenu = () => { + const { user, loading, signOut } = useAuth(); + const [authDialogOpen, setAuthDialogOpen] = useState(false); + const [authMode, setAuthMode] = useState<"login" | "signup">("login"); + + const handleOpenAuth = (mode: "login" | "signup") => { + setAuthMode(mode); + setAuthDialogOpen(true); + }; + + const handleSignOut = async () => { + await signOut(); + }; + + if (loading) { + return null; + } + + return ( + <> + + + + + Mitt konto + + + + + {user ? ( + <> + + {user.email} + + + + Logga ut + + > + ) : ( + <> + handleOpenAuth("signup")} + className="cursor-pointer flex items-center gap-2" + > + + Skapa konto + + handleOpenAuth("login")} + className="cursor-pointer flex items-center gap-2" + > + + Logga in + + > + )} + + + + + > + ); +}; + +export default AccountMenu; diff --git a/src/components/AuthDialog.tsx b/src/components/AuthDialog.tsx new file mode 100644 index 0000000..179d7ae --- /dev/null +++ b/src/components/AuthDialog.tsx @@ -0,0 +1,170 @@ +import { useState } from "react"; +import { z } from "zod"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { useAuth } from "@/hooks/useAuth"; +import { useToast } from "@/hooks/use-toast"; + +const authSchema = z.object({ + email: z.string().trim().email({ message: "Ogiltig e-postadress" }).max(255), + password: z.string().min(6, { message: "Lösenordet måste vara minst 6 tecken" }).max(100), +}); + +interface AuthDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + mode: "login" | "signup"; + onModeChange: (mode: "login" | "signup") => void; +} + +const AuthDialog = ({ open, onOpenChange, mode, onModeChange }: AuthDialogProps) => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + const { signIn, signUp } = useAuth(); + const { toast } = useToast(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const validation = authSchema.safeParse({ email, password }); + if (!validation.success) { + toast({ + title: "Fel", + description: validation.error.errors[0].message, + variant: "destructive", + }); + return; + } + + setIsSubmitting(true); + + try { + if (mode === "signup") { + const { error } = await signUp(email, password); + if (error) { + toast({ + title: "Kunde inte skapa konto", + description: error.message, + variant: "destructive", + }); + } else { + toast({ + title: "Konto skapat!", + description: "Du är nu inloggad.", + }); + onOpenChange(false); + resetForm(); + } + } else { + const { error } = await signIn(email, password); + if (error) { + toast({ + title: "Kunde inte logga in", + description: error.message, + variant: "destructive", + }); + } else { + toast({ + title: "Inloggad!", + description: "Välkommen tillbaka.", + }); + onOpenChange(false); + resetForm(); + } + } + } finally { + setIsSubmitting(false); + } + }; + + const resetForm = () => { + setEmail(""); + setPassword(""); + }; + + return ( + + + + + {mode === "signup" ? "Skapa konto" : "Logga in"} + + + + + + E-post + setEmail(e.target.value)} + placeholder="din@email.se" + required + /> + + + + Lösenord + setPassword(e.target.value)} + placeholder="••••••••" + required + /> + + + + {isSubmitting + ? "Laddar..." + : mode === "signup" + ? "Skapa konto" + : "Logga in"} + + + + + {mode === "signup" ? ( + + Har du redan ett konto?{" "} + onModeChange("login")} + className="text-primary hover:underline" + > + Logga in + + + ) : ( + + Har du inget konto?{" "} + onModeChange("signup")} + className="text-primary hover:underline" + > + Skapa konto + + + )} + + + + ); +}; + +export default AuthDialog; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 6d87bec..04998e3 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,12 +1,20 @@ -import logoImage from "@/assets/logo.png"; +import AccountMenu from "./AccountMenu"; + const Header = () => { - return + return ( + - + - + - ; + + ); }; + export default Header; \ No newline at end of file diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts new file mode 100644 index 0000000..b574bc9 --- /dev/null +++ b/src/hooks/useAuth.ts @@ -0,0 +1,62 @@ +import { useState, useEffect } from "react"; +import { User, Session } from "@supabase/supabase-js"; +import { supabase } from "@/integrations/supabase/client"; + +export function useAuth() { + const [user, setUser] = useState(null); + const [session, setSession] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Set up auth state listener BEFORE getting session + const { data: { subscription } } = supabase.auth.onAuthStateChange( + (_event, session) => { + setSession(session); + setUser(session?.user ?? null); + setLoading(false); + } + ); + + // Get initial session + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session); + setUser(session?.user ?? null); + setLoading(false); + }); + + return () => subscription.unsubscribe(); + }, []); + + const signUp = async (email: string, password: string) => { + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: window.location.origin, + }, + }); + return { error }; + }; + + const signIn = async (email: string, password: string) => { + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + return { error }; + }; + + const signOut = async () => { + const { error } = await supabase.auth.signOut(); + return { error }; + }; + + return { + user, + session, + loading, + signUp, + signIn, + signOut, + }; +} diff --git a/src/integrations/supabase/client.ts b/src/integrations/supabase/client.ts new file mode 100644 index 0000000..1619b47 --- /dev/null +++ b/src/integrations/supabase/client.ts @@ -0,0 +1,17 @@ +// This file is automatically generated. Do not edit it directly. +import { createClient } from '@supabase/supabase-js'; +import type { Database } from './types'; + +const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL; +const SUPABASE_PUBLISHABLE_KEY = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY; + +// Import the supabase client like this: +// import { supabase } from "@/integrations/supabase/client"; + +export const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, { + auth: { + storage: localStorage, + persistSession: true, + autoRefreshToken: true, + } +}); \ No newline at end of file diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts new file mode 100644 index 0000000..e375cad --- /dev/null +++ b/src/integrations/supabase/types.ts @@ -0,0 +1,184 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + // Allows to automatically instantiate createClient with right options + // instead of createClient(URL, KEY) + __InternalSupabase: { + PostgrestVersion: "14.1" + } + public: { + Tables: { + profiles: { + Row: { + address: string | null + created_at: string + display_name: string | null + id: string + phone: string | null + updated_at: string + user_id: string + } + Insert: { + address?: string | null + created_at?: string + display_name?: string | null + id?: string + phone?: string | null + updated_at?: string + user_id: string + } + Update: { + address?: string | null + created_at?: string + display_name?: string | null + id?: string + phone?: string | null + updated_at?: string + user_id?: string + } + Relationships: [] + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DatabaseWithoutInternals = Omit + +type DefaultSchema = DatabaseWithoutInternals[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof DatabaseWithoutInternals }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof DatabaseWithoutInternals }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof DatabaseWithoutInternals }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals + } + ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { + schema: keyof DatabaseWithoutInternals +} + ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + public: { + Enums: {}, + }, +} as const diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..1b5881b --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1 @@ +project_id = "teeojvlqulhghditoakc" \ No newline at end of file diff --git a/supabase/migrations/20260127223925_bfbb8689-adb7-4368-9b59-7600297cb8d8.sql b/supabase/migrations/20260127223925_bfbb8689-adb7-4368-9b59-7600297cb8d8.sql new file mode 100644 index 0000000..d971a8a --- /dev/null +++ b/supabase/migrations/20260127223925_bfbb8689-adb7-4368-9b59-7600297cb8d8.sql @@ -0,0 +1,62 @@ +-- Create profiles table for user data +CREATE TABLE public.profiles ( + id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE, + display_name TEXT, + phone TEXT, + address TEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() +); + +-- Enable Row Level Security +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; + +-- Users can view their own profile +CREATE POLICY "Users can view their own profile" +ON public.profiles +FOR SELECT +USING (auth.uid() = user_id); + +-- Users can insert their own profile +CREATE POLICY "Users can insert their own profile" +ON public.profiles +FOR INSERT +WITH CHECK (auth.uid() = user_id); + +-- Users can update their own profile +CREATE POLICY "Users can update their own profile" +ON public.profiles +FOR UPDATE +USING (auth.uid() = user_id); + +-- Create function to update timestamps +CREATE OR REPLACE FUNCTION public.update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SET search_path = public; + +-- Create trigger for automatic timestamp updates +CREATE TRIGGER update_profiles_updated_at +BEFORE UPDATE ON public.profiles +FOR EACH ROW +EXECUTE FUNCTION public.update_updated_at_column(); + +-- Create function to automatically create profile on signup +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (user_id) + VALUES (NEW.id); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = public; + +-- Create trigger to auto-create profile on user signup +CREATE TRIGGER on_auth_user_created +AFTER INSERT ON auth.users +FOR EACH ROW +EXECUTE FUNCTION public.handle_new_user(); \ No newline at end of file
+ Har du redan ett konto?{" "} + onModeChange("login")} + className="text-primary hover:underline" + > + Logga in + +
+ Har du inget konto?{" "} + onModeChange("signup")} + className="text-primary hover:underline" + > + Skapa konto + +