Add Supabase client setup and authentication functions

This commit is contained in:
2026-01-20 17:14:50 +00:00
parent dc4712d371
commit 9cd34d3ff1
7 changed files with 235 additions and 57 deletions

View File

@@ -1,18 +1,3 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Smart Agenda</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<html lang="pt-BR"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Smart Agenda</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>

135
web/package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "smart-agenda-web",
"version": "0.1.0",
"dependencies": {
"@supabase/supabase-js": "^2.91.0",
"classnames": "^2.5.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.473.0",
@@ -1138,6 +1139,86 @@
"win32"
]
},
"node_modules/@supabase/auth-js": {
"version": "2.91.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.91.0.tgz",
"integrity": "sha512-9ywvsKLsxTwv7fvN5fXzP3UfRreqrX2waylTBDu0lkmeHXa8WtSQS9e0WV9FBduiazYqQbgfBQXBNPRPsRgWOQ==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.91.0",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.91.0.tgz",
"integrity": "sha512-WaakXOqLK1mLtBNFXp5o5T+LlI6KZuADSeXz+9ofPRG5OpVSvW148LVJB1DRZ16Phck1a0YqIUswOUgxCz6vMw==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "2.91.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.91.0.tgz",
"integrity": "sha512-5S41zv2euNpGucvtM4Wy+xOmLznqt/XO+Lh823LOFEQ00ov7QJfvqb6VzIxufvzhooZpmGR0BxvMcJtWxCIFdQ==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.91.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.91.0.tgz",
"integrity": "sha512-u2YuJFG35umw8DO9beC27L/jYXm3KhF+73WQwbynMpV0tXsFIA0DOGRM0NgRyy03hJIdO6mxTTwe8efW3yx3Tg==",
"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.91.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.91.0.tgz",
"integrity": "sha512-CI7fsVIBQHfNObqU9kmyQ1GWr+Ug44y4rSpvxT4LdQB9tlhg1NTBov6z7Dlmt8d6lGi/8a9lf/epCDxyWI792g==",
"license": "MIT",
"dependencies": {
"iceberg-js": "^0.8.1",
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.91.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.91.0.tgz",
"integrity": "sha512-Rjb0QqkKrmXMVwUOdEqysPBZ0ZDZakeptTkUa6k2d8r3strBdbWVDqjOdkCjAmvvZMtXecBeyTyMEXD1Zzjfvg==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.91.0",
"@supabase/functions-js": "2.91.0",
"@supabase/postgrest-js": "2.91.0",
"@supabase/realtime-js": "2.91.0",
"@supabase/storage-js": "2.91.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1257,13 +1338,17 @@
"version": "22.19.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz",
"integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
"dev": true,
"license": "MIT",
"peer": true,
"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.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
@@ -1293,6 +1378,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/@vitejs/plugin-react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
@@ -1949,6 +2043,15 @@
"node": ">= 0.4"
}
},
"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/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
@@ -2955,6 +3058,12 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": {
"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/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -2973,7 +3082,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/update-browserslist-db": {
@@ -3106,6 +3214,27 @@
}
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@@ -10,6 +10,7 @@
"lint": "tsc --noEmit"
},
"dependencies": {
"@supabase/supabase-js": "^2.91.0",
"classnames": "^2.5.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.473.0",
@@ -32,6 +33,3 @@
"vite": "^5.4.10"
}
}

View File

@@ -2,6 +2,20 @@ import { RouterProvider } from 'react-router-dom';
import { router } from './routes';
const App = () => <RouterProvider router={router} />;
import { useEffect } from 'react'
import { signIn } from './lib/auth'
useEffect(() => {
;(async () => {
try {
await signIn('TEU_EMAIL', 'TUA_PASSWORD')
console.log('LOGIN OK')
} catch (e: any) {
console.log('LOGIN ERRO:', e.message)
}
})()
}, [])
export default App;

View File

@@ -1,38 +1,42 @@
import { Link, useNavigate } from 'react-router-dom';
import { MapPin, ShoppingCart, User, LogOut, Menu, X } from 'lucide-react';
import { Button } from '../ui/button';
import { useApp } from '../../context/AppContext';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom'
import { MapPin, ShoppingCart, User, LogOut, Menu, X } from 'lucide-react'
import { useApp } from '../../context/AppContext'
import { useState } from 'react'
export const Header = () => {
const { user, cart, logout } = useApp();
const navigate = useNavigate();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const { user, cart, logout } = useApp()
const navigate = useNavigate()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const handleLogout = () => {
logout();
navigate('/');
setMobileMenuOpen(false);
};
logout()
navigate('/')
setMobileMenuOpen(false)
}
return (
<header className="sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-slate-200/60 shadow-sm">
<div className="mx-auto flex h-16 max-w-5xl items-center justify-between px-4">
<Link to="/" className="text-xl font-bold bg-gradient-to-r from-indigo-600 to-blue-700 bg-clip-text text-transparent hover:from-indigo-700 hover:to-blue-800 transition-all">
<Link
to="/"
className="text-xl font-bold bg-gradient-to-r from-indigo-600 to-blue-700 bg-clip-text text-transparent hover:from-indigo-700 hover:to-blue-800 transition-all"
onClick={() => setMobileMenuOpen(false)}
>
Smart Agenda
</Link>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-4">
<Link
to="/explorar"
<Link
to="/explorar"
className="flex items-center gap-1.5 text-sm font-medium text-slate-700 hover:text-indigo-600 transition-colors px-3 py-1.5 rounded-lg hover:bg-indigo-50"
>
<MapPin size={16} />
<span>Barbearias</span>
</Link>
<Link
to="/carrinho"
<Link
to="/carrinho"
className="relative text-slate-700 hover:text-indigo-600 transition-colors p-2 rounded-lg hover:bg-indigo-50"
>
<ShoppingCart size={18} />
@@ -42,27 +46,34 @@ export const Header = () => {
</span>
)}
</Link>
{user ? (
<div className="flex items-center gap-2">
<button
onClick={() => navigate(user.role === 'barbearia' ? '/painel' : '/perfil')}
<button
onClick={() => navigate(user.role === 'barbearia' ? '/painel' : '/perfil')}
className="flex items-center gap-1.5 text-sm font-medium text-slate-700 hover:text-indigo-600 transition-colors px-3 py-1.5 rounded-lg hover:bg-indigo-50"
type="button"
>
<User size={16} />
<span className="max-w-[120px] truncate">{user.name}</span>
</button>
<button
onClick={handleLogout}
className="p-2 text-slate-600 hover:text-rose-600 hover:bg-rose-50 rounded-lg transition-colors"
title="Sair"
type="button"
>
<LogOut size={16} />
</button>
</div>
) : (
<Button asChild variant="outline" size="sm">
<Link to="/login">Entrar</Link>
</Button>
<Link
to="/login"
className="inline-flex items-center justify-center rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-800 shadow-sm hover:bg-slate-50 transition-colors"
>
Entrar
</Link>
)}
</nav>
@@ -70,6 +81,7 @@ export const Header = () => {
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2 text-slate-700 hover:text-amber-600 hover:bg-amber-50 rounded-lg transition-colors"
type="button"
>
{mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
</button>
@@ -87,6 +99,7 @@ export const Header = () => {
<MapPin size={16} />
Barbearias
</Link>
<Link
to="/carrinho"
onClick={() => setMobileMenuOpen(false)}
@@ -100,39 +113,42 @@ export const Header = () => {
</span>
)}
</Link>
{user ? (
<>
<button
onClick={() => {
navigate(user.role === 'barbearia' ? '/painel' : '/perfil');
setMobileMenuOpen(false);
navigate(user.role === 'barbearia' ? '/painel' : '/perfil')
setMobileMenuOpen(false)
}}
className="w-full flex items-center gap-2 text-sm font-medium text-slate-700 hover:text-amber-600 transition-colors px-3 py-2 rounded-lg hover:bg-amber-50 text-left"
type="button"
>
<User size={16} />
{user.name}
</button>
<button
onClick={handleLogout}
className="w-full flex items-center gap-2 text-sm font-medium text-rose-600 hover:bg-rose-50 transition-colors px-3 py-2 rounded-lg text-left"
type="button"
>
<LogOut size={16} />
Sair
</button>
</>
) : (
<Button asChild variant="solid" size="sm" className="w-full">
<Link to="/login" onClick={() => setMobileMenuOpen(false)}>Entrar</Link>
</Button>
<Link
to="/login"
onClick={() => setMobileMenuOpen(false)}
className="inline-flex w-full items-center justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-700 transition-colors"
>
Entrar
</Link>
)}
</nav>
</div>
)}
</header>
);
};
)
}

29
web/src/lib/auth.ts Normal file
View File

@@ -0,0 +1,29 @@
import { supabase } from './supabase'
export async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) throw error
return data
}
export async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
})
if (error) throw error
return data
}
export async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
export async function getUser() {
const { data } = await supabase.auth.getUser()
return data.user
}

7
web/src/lib/supabase.ts Normal file
View File

@@ -0,0 +1,7 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://jqklhhpyykzrktikjnmb.supabase.co'
const supabaseAnonKey =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Impxa2xoaHB5eWt6cmt0aWtqbm1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjgzODQ0MDgsImV4cCI6MjA4Mzk2MDQwOH0.QsPuBnyUtRPSavlqKj3IGR9c8juT02LY_hSi-j3c6M0'
export const supabase = createClient(supabaseUrl, supabaseAnonKey)