Revert "RBAC in place. Tailwind CSS working."
This reverts commit 90d874b15f.
This commit is contained in:
148
src/App.tsx
148
src/App.tsx
@@ -1,126 +1,34 @@
|
||||
import React from 'react'
|
||||
import { AuthProvider, useAuth } from './contexts/AuthContext'
|
||||
import { LoginForm } from './components/auth/LoginForm'
|
||||
import { UserProfile } from './components/auth/UserProfile'
|
||||
import { ProtectedRoute, AdminOnly, ModeratorOrAdmin, AuthenticatedOnly } from './components/auth/ProtectedRoute'
|
||||
|
||||
const AppContent: React.FC = () => {
|
||||
const { user, loading } = useAuth()
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-xl">Loading...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 py-8">
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-3xl font-bold text-center mb-8">RBAC Demo Application</h1>
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 py-8">
|
||||
<div className="container mx-auto px-4">
|
||||
<h1 className="text-3xl font-bold text-center mb-8">RBAC Demo Application</h1>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
{/* User Profile Section */}
|
||||
<div>
|
||||
<UserProfile />
|
||||
</div>
|
||||
|
||||
{/* Role-Based Content Section */}
|
||||
<div className="space-y-6">
|
||||
{/* Content for all authenticated users */}
|
||||
<AuthenticatedOnly>
|
||||
<div className="bg-green-50 p-4 rounded-lg border border-green-200">
|
||||
<h3 className="text-lg font-semibold text-green-800 mb-2">
|
||||
✅ Authenticated User Content
|
||||
</h3>
|
||||
<p className="text-green-700">
|
||||
This content is visible to all authenticated users.
|
||||
</p>
|
||||
</div>
|
||||
</AuthenticatedOnly>
|
||||
|
||||
{/* Content for moderators and admins */}
|
||||
<ModeratorOrAdmin>
|
||||
<div className="bg-yellow-50 p-4 rounded-lg border border-yellow-200">
|
||||
<h3 className="text-lg font-semibold text-yellow-800 mb-2">
|
||||
🛡️ Moderator/Admin Content
|
||||
</h3>
|
||||
<p className="text-yellow-700">
|
||||
This content is visible to moderators and administrators only.
|
||||
</p>
|
||||
</div>
|
||||
</ModeratorOrAdmin>
|
||||
|
||||
{/* Content for admins only */}
|
||||
<AdminOnly>
|
||||
<div className="bg-red-50 p-4 rounded-lg border border-red-200">
|
||||
<h3 className="text-lg font-semibold text-red-800 mb-2">
|
||||
🔑 Admin Only Content
|
||||
</h3>
|
||||
<p className="text-red-700">
|
||||
This content is visible to administrators only. You have full system access.
|
||||
</p>
|
||||
</div>
|
||||
</AdminOnly>
|
||||
|
||||
{/* Custom role check example */}
|
||||
<ProtectedRoute
|
||||
requiredRole="user"
|
||||
fallback={
|
||||
<div className="bg-gray-50 p-4 rounded-lg border border-gray-200">
|
||||
<p className="text-gray-600">You need 'user' role to see this content.</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="bg-blue-50 p-4 rounded-lg border border-blue-200">
|
||||
<h3 className="text-lg font-semibold text-blue-800 mb-2">
|
||||
👤 User Role Content
|
||||
</h3>
|
||||
<p className="text-blue-700">
|
||||
This content is visible to users with the 'user' role.
|
||||
</p>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="mt-8 bg-white p-6 rounded-lg shadow-md">
|
||||
<h2 className="text-xl font-bold mb-4">RBAC System Instructions</h2>
|
||||
<div className="space-y-2 text-sm text-gray-600">
|
||||
<p><strong>Admin User:</strong> s.alireza.v@gmail.com (password: ???????)</p>
|
||||
<p><strong>Features:</strong></p>
|
||||
<ul className="list-disc list-inside ml-4 space-y-1">
|
||||
<li>Role-based content visibility</li>
|
||||
<li>Protected routes and components</li>
|
||||
<li>User profile with role display</li>
|
||||
<li>Secure authentication with Supabase</li>
|
||||
<li>Row Level Security (RLS) policies</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from '/vite.svg'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<AuthProvider>
|
||||
<AppContent />
|
||||
</AuthProvider>
|
||||
<>
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank">
|
||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + React</h1>
|
||||
<div className="card">
|
||||
<button onClick={() => setCount((count) => count + 1)}>
|
||||
count is {count}
|
||||
</button>
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to test HMR
|
||||
</p>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
Click on the Vite and React logos to learn more
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useAuth } from '../../contexts/AuthContext'
|
||||
|
||||
export const LoginForm: React.FC = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const { signIn } = useAuth()
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const { error } = await signIn(email, password)
|
||||
if (error) {
|
||||
setError(error.message)
|
||||
}
|
||||
} catch (err) {
|
||||
setError('An unexpected error occurred')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold mb-6 text-center">Sign In</h2>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter your password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? 'Signing in...' : 'Sign In'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-4 text-sm text-gray-600">
|
||||
<p>Test admin credentials:</p>
|
||||
<p>Email: s.alireza.v@gmail.com</p>
|
||||
<p>Password: ???????</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useAuth } from '../../contexts/AuthContext'
|
||||
import type { RoleName } from '../../types/auth'
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: React.ReactNode
|
||||
requiredRole?: RoleName
|
||||
requiredRoles?: RoleName[]
|
||||
fallback?: React.ReactNode
|
||||
requireAuth?: boolean
|
||||
}
|
||||
|
||||
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
|
||||
children,
|
||||
requiredRole,
|
||||
requiredRoles,
|
||||
fallback = <div className="text-red-500">Access denied. You don't have permission to view this content.</div>,
|
||||
requireAuth = true
|
||||
}) => {
|
||||
const { user, loading } = useAuth()
|
||||
|
||||
// Show loading state
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<div className="text-gray-500">Loading...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Check if authentication is required
|
||||
if (requireAuth && !user) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<div className="text-red-500">Please sign in to access this content.</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Check single required role
|
||||
if (requiredRole && user && !user.roles?.includes(requiredRole)) {
|
||||
return <>{fallback}</>
|
||||
}
|
||||
|
||||
// Check multiple required roles (user must have at least one)
|
||||
if (requiredRoles && user && !requiredRoles.some(role => user.roles?.includes(role))) {
|
||||
return <>{fallback}</>
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
// Convenience components for common role checks
|
||||
export const AdminOnly: React.FC<{ children: React.ReactNode; fallback?: React.ReactNode }> = ({
|
||||
children,
|
||||
fallback
|
||||
}) => (
|
||||
<ProtectedRoute requiredRole="admin" fallback={fallback}>
|
||||
{children}
|
||||
</ProtectedRoute>
|
||||
)
|
||||
|
||||
export const ModeratorOrAdmin: React.FC<{ children: React.ReactNode; fallback?: React.ReactNode }> = ({
|
||||
children,
|
||||
fallback
|
||||
}) => (
|
||||
<ProtectedRoute requiredRoles={['admin', 'moderator']} fallback={fallback}>
|
||||
{children}
|
||||
</ProtectedRoute>
|
||||
)
|
||||
|
||||
export const AuthenticatedOnly: React.FC<{ children: React.ReactNode; fallback?: React.ReactNode }> = ({
|
||||
children,
|
||||
fallback
|
||||
}) => (
|
||||
<ProtectedRoute requireAuth={true} fallback={fallback}>
|
||||
{children}
|
||||
</ProtectedRoute>
|
||||
)
|
||||
@@ -1,78 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useAuth } from '../../contexts/AuthContext'
|
||||
|
||||
export const UserProfile: React.FC = () => {
|
||||
const { user, signOut, isAdmin } = useAuth()
|
||||
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold mb-4">User Profile</h2>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Email</label>
|
||||
<p className="text-gray-900">{user.email}</p>
|
||||
</div>
|
||||
|
||||
{user.profile && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">First Name</label>
|
||||
<p className="text-gray-900">{user.profile.first_name || 'Not set'}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Last Name</label>
|
||||
<p className="text-gray-900">{user.profile.last_name || 'Not set'}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Roles</label>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{user.roles && user.roles.length > 0 ? (
|
||||
user.roles.map((role) => (
|
||||
<span
|
||||
key={role}
|
||||
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
role === 'admin'
|
||||
? 'bg-red-100 text-red-800'
|
||||
: role === 'moderator'
|
||||
? 'bg-yellow-100 text-yellow-800'
|
||||
: 'bg-blue-100 text-blue-800'
|
||||
}`}
|
||||
>
|
||||
{role}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span className="text-gray-500">No roles assigned</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isAdmin() && (
|
||||
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded">
|
||||
<p className="text-sm text-red-700 font-medium">
|
||||
🔑 You have administrator privileges
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pt-4">
|
||||
<button
|
||||
onClick={signOut}
|
||||
className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
import type { User } from '@supabase/supabase-js'
|
||||
import { supabase } from '../lib/supabase'
|
||||
import type { AuthContextType, AuthUser, UserProfile } from '../types/auth'
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const [user, setUser] = useState<AuthUser | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
// Fetch user profile and roles
|
||||
const fetchUserData = async (authUser: User): Promise<AuthUser> => {
|
||||
try {
|
||||
// Fetch user profile
|
||||
const { data: profile, error: profileError } = await supabase
|
||||
.from('user_profiles')
|
||||
.select('*')
|
||||
.eq('id', authUser.id)
|
||||
.single()
|
||||
|
||||
if (profileError && profileError.code !== 'PGRST116') {
|
||||
console.error('Error fetching user profile:', profileError)
|
||||
}
|
||||
|
||||
// Fetch user roles using the database function
|
||||
const { data: rolesData, error: rolesError } = await supabase
|
||||
.rpc('get_user_roles', { user_uuid: authUser.id })
|
||||
|
||||
if (rolesError) {
|
||||
console.error('Error fetching user roles:', rolesError)
|
||||
}
|
||||
|
||||
const roles = rolesData?.map(r => r.role_name) || []
|
||||
|
||||
return {
|
||||
...authUser,
|
||||
profile: profile as UserProfile,
|
||||
roles
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user data:', error)
|
||||
return {
|
||||
...authUser,
|
||||
profile: undefined,
|
||||
roles: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh user data
|
||||
const refreshUserData = async () => {
|
||||
const { data: { user: authUser } } = await supabase.auth.getUser()
|
||||
if (authUser) {
|
||||
const userData = await fetchUserData(authUser)
|
||||
setUser(userData)
|
||||
}
|
||||
}
|
||||
|
||||
// Sign in
|
||||
const signIn = async (email: string, password: string) => {
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password
|
||||
})
|
||||
return { error }
|
||||
}
|
||||
|
||||
// Sign up
|
||||
const signUp = async (
|
||||
email: string,
|
||||
password: string,
|
||||
userData?: { first_name?: string; last_name?: string }
|
||||
) => {
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: userData
|
||||
}
|
||||
})
|
||||
|
||||
// If signup successful and user data provided, create profile
|
||||
if (!error && data.user && userData) {
|
||||
const { error: profileError } = await supabase
|
||||
.from('user_profiles')
|
||||
.insert({
|
||||
id: data.user.id,
|
||||
first_name: userData.first_name,
|
||||
last_name: userData.last_name
|
||||
})
|
||||
|
||||
if (profileError) {
|
||||
console.error('Error creating user profile:', profileError)
|
||||
}
|
||||
|
||||
// Assign default 'user' role
|
||||
const { error: roleError } = await supabase
|
||||
.from('user_roles')
|
||||
.insert({
|
||||
user_id: data.user.id,
|
||||
role_id: 6 // 'user' role ID from our database
|
||||
})
|
||||
|
||||
if (roleError) {
|
||||
console.error('Error assigning default role:', roleError)
|
||||
}
|
||||
}
|
||||
|
||||
return { error }
|
||||
}
|
||||
|
||||
// Sign out
|
||||
const signOut = async () => {
|
||||
await supabase.auth.signOut()
|
||||
setUser(null)
|
||||
}
|
||||
|
||||
// Check if user has specific role
|
||||
const hasRole = (roleName: string): boolean => {
|
||||
return user?.roles?.includes(roleName) || false
|
||||
}
|
||||
|
||||
// Check if user is admin
|
||||
const isAdmin = (): boolean => {
|
||||
return hasRole('admin')
|
||||
}
|
||||
|
||||
// Handle auth state changes
|
||||
useEffect(() => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||
async (event, session) => {
|
||||
if (session?.user) {
|
||||
const userData = await fetchUserData(session.user)
|
||||
setUser(userData)
|
||||
} else {
|
||||
setUser(null)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
)
|
||||
|
||||
return () => subscription.unsubscribe()
|
||||
}, [])
|
||||
|
||||
const value: AuthContextType = {
|
||||
user,
|
||||
loading,
|
||||
signIn,
|
||||
signUp,
|
||||
signOut,
|
||||
hasRole,
|
||||
isAdmin,
|
||||
refreshUserData
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -1 +1,68 @@
|
||||
@import "tailwindcss";
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Local Supabase instance configuration
|
||||
const supabaseUrl = 'http://127.0.0.1:54321'
|
||||
const supabaseAnonKey = '[REDACTED]'
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
|
||||
// Database types for TypeScript
|
||||
export interface Database {
|
||||
public: {
|
||||
Tables: {
|
||||
roles: {
|
||||
Row: {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
id?: number
|
||||
name: string
|
||||
description?: string | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
Update: {
|
||||
id?: number
|
||||
name?: string
|
||||
description?: string | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
}
|
||||
user_profiles: {
|
||||
Row: {
|
||||
id: string
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
id: string
|
||||
first_name?: string | null
|
||||
last_name?: string | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
Update: {
|
||||
id?: string
|
||||
first_name?: string | null
|
||||
last_name?: string | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
}
|
||||
user_roles: {
|
||||
Row: {
|
||||
id: number
|
||||
user_id: string | null
|
||||
role_id: number | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
id?: number
|
||||
user_id?: string | null
|
||||
role_id?: number | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
Update: {
|
||||
id?: number
|
||||
user_id?: string | null
|
||||
role_id?: number | null
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
get_user_roles: {
|
||||
Args: {
|
||||
user_uuid: string
|
||||
}
|
||||
Returns: {
|
||||
role_name: string
|
||||
}[]
|
||||
}
|
||||
user_has_role: {
|
||||
Args: {
|
||||
user_uuid: string
|
||||
role_name: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { User } from '@supabase/supabase-js'
|
||||
|
||||
export interface UserProfile {
|
||||
id: string
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
id: number
|
||||
name: string
|
||||
description: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface UserRole {
|
||||
id: number
|
||||
user_id: string | null
|
||||
role_id: number | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface AuthUser extends User {
|
||||
profile?: UserProfile
|
||||
roles?: string[]
|
||||
}
|
||||
|
||||
export interface AuthContextType {
|
||||
user: AuthUser | null
|
||||
loading: boolean
|
||||
signIn: (email: string, password: string) => Promise<{ error: any }>
|
||||
signUp: (email: string, password: string, userData?: { first_name?: string; last_name?: string }) => Promise<{ error: any }>
|
||||
signOut: () => Promise<void>
|
||||
hasRole: (roleName: string) => boolean
|
||||
isAdmin: () => boolean
|
||||
refreshUserData: () => Promise<void>
|
||||
}
|
||||
|
||||
export type RoleName = 'admin' | 'user' | 'moderator' | 'coordinator' | 'conductor' | 'analyst'
|
||||
Reference in New Issue
Block a user