mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
feat: Centralize Auth Modal and require auth for actions
This commit is contained in:
@@ -7,6 +7,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
import { AuthProvider } from "@/hooks/useAuth";
|
||||
import { LocationAutoDetectProvider } from "@/components/providers/LocationAutoDetectProvider";
|
||||
import { AuthModalProvider } from "@/contexts/AuthModalContext";
|
||||
import { Footer } from "@/components/layout/Footer";
|
||||
import Index from "./pages/Index";
|
||||
import Parks from "./pages/Parks";
|
||||
@@ -127,7 +128,9 @@ function AppContent() {
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<AuthModalProvider>
|
||||
<AppContent />
|
||||
</AuthModalProvider>
|
||||
</AuthProvider>
|
||||
{import.meta.env.DEV && (
|
||||
<ReactQueryDevtools
|
||||
|
||||
@@ -7,18 +7,15 @@ import { User, Settings, LogOut } from 'lucide-react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useProfile } from '@/hooks/useProfile';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { AuthModal } from './AuthModal';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export function AuthButtons() {
|
||||
const { user, loading: authLoading, signOut } = useAuth();
|
||||
const { data: profile, isLoading: profileLoading } = useProfile(user?.id);
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
toast
|
||||
} = useToast();
|
||||
const { toast } = useToast();
|
||||
const { openAuthModal } = useAuthModal();
|
||||
const [loggingOut, setLoggingOut] = useState(false);
|
||||
const [authModalOpen, setAuthModalOpen] = useState(false);
|
||||
const [authModalTab, setAuthModalTab] = useState<'signin' | 'signup'>('signin');
|
||||
const handleSignOut = async () => {
|
||||
setLoggingOut(true);
|
||||
try {
|
||||
@@ -55,28 +52,17 @@ export function AuthButtons() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setAuthModalTab('signin');
|
||||
setAuthModalOpen(true);
|
||||
}}
|
||||
onClick={() => openAuthModal('signin')}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-gradient-to-r from-primary to-accent hover:from-primary/90 hover:to-accent/90"
|
||||
onClick={() => {
|
||||
setAuthModalTab('signup');
|
||||
setAuthModalOpen(true);
|
||||
}}
|
||||
onClick={() => openAuthModal('signup')}
|
||||
>
|
||||
Join ThrillWiki
|
||||
</Button>
|
||||
<AuthModal
|
||||
open={authModalOpen}
|
||||
onOpenChange={setAuthModalOpen}
|
||||
defaultTab={authModalTab}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
59
src/contexts/AuthModalContext.tsx
Normal file
59
src/contexts/AuthModalContext.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { createContext, useState, ReactNode } from 'react';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import { AuthModal } from '@/components/auth/AuthModal';
|
||||
|
||||
interface AuthModalContextType {
|
||||
openAuthModal: (defaultTab?: 'signin' | 'signup') => void;
|
||||
requireAuth: (callback: () => void, message?: string) => void;
|
||||
authModalOpen: boolean;
|
||||
setAuthModalOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const AuthModalContext = createContext<AuthModalContextType | undefined>(undefined);
|
||||
|
||||
interface AuthModalProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function AuthModalProvider({ children }: AuthModalProviderProps) {
|
||||
const { user } = useAuth();
|
||||
const { toast } = useToast();
|
||||
const [authModalOpen, setAuthModalOpen] = useState(false);
|
||||
const [authModalTab, setAuthModalTab] = useState<'signin' | 'signup'>('signin');
|
||||
|
||||
const openAuthModal = (defaultTab: 'signin' | 'signup' = 'signin') => {
|
||||
setAuthModalTab(defaultTab);
|
||||
setAuthModalOpen(true);
|
||||
};
|
||||
|
||||
const requireAuth = (callback: () => void, message?: string) => {
|
||||
if (user) {
|
||||
callback();
|
||||
} else {
|
||||
toast({
|
||||
title: "Authentication Required",
|
||||
description: message || "Please sign in to continue",
|
||||
});
|
||||
openAuthModal('signin');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthModalContext.Provider
|
||||
value={{
|
||||
openAuthModal,
|
||||
requireAuth,
|
||||
authModalOpen,
|
||||
setAuthModalOpen,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<AuthModal
|
||||
open={authModalOpen}
|
||||
onOpenChange={setAuthModalOpen}
|
||||
defaultTab={authModalTab}
|
||||
/>
|
||||
</AuthModalContext.Provider>
|
||||
);
|
||||
}
|
||||
10
src/hooks/useAuthModal.tsx
Normal file
10
src/hooks/useAuthModal.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useContext } from 'react';
|
||||
import { AuthModalContext } from '@/contexts/AuthModalContext';
|
||||
|
||||
export const useAuthModal = () => {
|
||||
const context = useContext(AuthModalContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuthModal must be used within AuthModalProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -14,11 +14,13 @@ import { RideForm } from '@/components/admin/RideForm';
|
||||
import { AutocompleteSearch } from '@/components/search/AutocompleteSearch';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function DesignerRides() {
|
||||
const { designerSlug } = useParams<{ designerSlug: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [designer, setDesigner] = useState<Company | null>(null);
|
||||
const [rides, setRides] = useState<Ride[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -100,8 +102,7 @@ export default function DesignerRides() {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
if (!user || !designer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,12 +210,12 @@ export default function DesignerRides() {
|
||||
<FerrisWheel className="w-8 h-8 text-primary" />
|
||||
<h1 className="text-4xl font-bold">Rides by {designer.name}</h1>
|
||||
</div>
|
||||
{user && (
|
||||
<Button onClick={() => setIsCreateModalOpen(true)}>
|
||||
<Button
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a new ride")}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Ride
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground mb-4">
|
||||
Explore all rides designed by {designer.name}
|
||||
|
||||
@@ -15,11 +15,13 @@ import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { submitCompanyCreation } from '@/lib/companyHelpers';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function Designers() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -28,10 +30,7 @@ export default function Designers() {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
if (!user) return;
|
||||
await submitCompanyCreation(data, 'designer', user.id);
|
||||
toast({
|
||||
title: "Designer Submitted",
|
||||
@@ -122,13 +121,7 @@ export default function Designers() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
} else {
|
||||
setIsCreateModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a designer")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
@@ -14,11 +14,13 @@ import { RideModelForm } from '@/components/admin/RideModelForm';
|
||||
import { AutocompleteSearch } from '@/components/search/AutocompleteSearch';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function ManufacturerModels() {
|
||||
const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [manufacturer, setManufacturer] = useState<Company | null>(null);
|
||||
const [models, setModels] = useState<RideModel[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -93,8 +95,7 @@ export default function ManufacturerModels() {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
if (!user || !manufacturer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,12 +195,12 @@ export default function ManufacturerModels() {
|
||||
<FerrisWheel className="w-8 h-8 text-primary" />
|
||||
<h1 className="text-4xl font-bold">Models by {manufacturer.name}</h1>
|
||||
</div>
|
||||
{user && (
|
||||
<Button onClick={() => setIsCreateModalOpen(true)}>
|
||||
<Button
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a ride model")}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Ride Model
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground mb-4">
|
||||
Explore all ride models manufactured by {manufacturer.name}
|
||||
|
||||
@@ -14,11 +14,13 @@ import { RideForm } from '@/components/admin/RideForm';
|
||||
import { AutocompleteSearch } from '@/components/search/AutocompleteSearch';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function ManufacturerRides() {
|
||||
const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [manufacturer, setManufacturer] = useState<Company | null>(null);
|
||||
const [rides, setRides] = useState<Ride[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -100,8 +102,7 @@ export default function ManufacturerRides() {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
if (!user || !manufacturer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,12 +210,12 @@ export default function ManufacturerRides() {
|
||||
<FerrisWheel className="w-8 h-8 text-primary" />
|
||||
<h1 className="text-4xl font-bold">Rides by {manufacturer.name}</h1>
|
||||
</div>
|
||||
{user && (
|
||||
<Button onClick={() => setIsCreateModalOpen(true)}>
|
||||
<Button
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a new ride")}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Ride
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground mb-4">
|
||||
Explore all rides manufactured by {manufacturer.name}
|
||||
|
||||
@@ -15,11 +15,13 @@ import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { submitCompanyCreation } from '@/lib/companyHelpers';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function Manufacturers() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -66,10 +68,7 @@ export default function Manufacturers() {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
if (!user) return;
|
||||
|
||||
await submitCompanyCreation(
|
||||
data,
|
||||
@@ -136,13 +135,7 @@ export default function Manufacturers() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
} else {
|
||||
setIsCreateModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a manufacturer")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
@@ -16,11 +16,13 @@ import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { submitCompanyCreation } from '@/lib/companyHelpers';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
const Operators = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterBy, setFilterBy] = useState('all');
|
||||
@@ -59,10 +61,7 @@ const Operators = () => {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
if (!user) return;
|
||||
|
||||
await submitCompanyCreation(
|
||||
data,
|
||||
@@ -147,13 +146,7 @@ const Operators = () => {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
} else {
|
||||
setIsCreateModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add an operator")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
@@ -16,11 +16,13 @@ import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { submitCompanyCreation } from '@/lib/companyHelpers';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
const ParkOwners = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [sortBy, setSortBy] = useState('name');
|
||||
const [filterBy, setFilterBy] = useState('all');
|
||||
@@ -59,10 +61,7 @@ const ParkOwners = () => {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
if (!user) return;
|
||||
|
||||
await submitCompanyCreation(
|
||||
data,
|
||||
@@ -147,13 +146,7 @@ const ParkOwners = () => {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
} else {
|
||||
setIsCreateModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a property owner")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
@@ -14,11 +14,13 @@ import { RideSubmissionData } from '@/types/submission-data';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function ParkRides() {
|
||||
const { parkSlug } = useParams<{ parkSlug: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [park, setPark] = useState<Park | null>(null);
|
||||
const [rides, setRides] = useState<Ride[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -223,13 +225,7 @@ export default function ParkRides() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
} else {
|
||||
setIsCreateModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a new ride")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
@@ -31,6 +31,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f
|
||||
import { ParkForm } from '@/components/admin/ParkForm';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export interface FilterState {
|
||||
search: string;
|
||||
@@ -80,6 +81,7 @@ export default function Parks() {
|
||||
const { toast } = useToast();
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
|
||||
useEffect(() => {
|
||||
fetchParks();
|
||||
@@ -235,19 +237,8 @@ export default function Parks() {
|
||||
navigate(`/parks/${park.slug}`);
|
||||
};
|
||||
|
||||
const handleAddParkClick = () => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
setIsAddParkModalOpen(true);
|
||||
};
|
||||
|
||||
const handleParkSubmit = async (parkData: any) => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
if (!user) return;
|
||||
|
||||
try {
|
||||
const { submitParkCreation } = await import('@/lib/entitySubmissionHelpers');
|
||||
@@ -321,7 +312,7 @@ export default function Parks() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={handleAddParkClick}
|
||||
onClick={() => requireAuth(() => setIsAddParkModalOpen(true), "Sign in to add a new park")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
@@ -14,11 +14,13 @@ import { supabase } from '@/integrations/supabase/client';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useUserRole } from '@/hooks/useUserRole';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { useAuthModal } from '@/hooks/useAuthModal';
|
||||
|
||||
export default function Rides() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { isModerator } = useUserRole();
|
||||
const { requireAuth } = useAuthModal();
|
||||
const [rides, setRides] = useState<Ride[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -78,10 +80,7 @@ export default function Rides() {
|
||||
|
||||
const handleCreateSubmit = async (data: any) => {
|
||||
try {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
return;
|
||||
}
|
||||
if (!user) return;
|
||||
|
||||
const { submitRideCreation } = await import('@/lib/entitySubmissionHelpers');
|
||||
await submitRideCreation(data, user.id);
|
||||
@@ -171,13 +170,7 @@ export default function Rides() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!user) {
|
||||
navigate('/auth');
|
||||
} else {
|
||||
setIsCreateModalOpen(true);
|
||||
}
|
||||
}}
|
||||
onClick={() => requireAuth(() => setIsCreateModalOpen(true), "Sign in to add a new ride")}
|
||||
className="gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
|
||||
Reference in New Issue
Block a user