Initial Commit
This commit is contained in:
36
src/components/auth/ProtectedRoute.tsx
Normal file
36
src/components/auth/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useAuth } from '@clerk/clerk-react';
|
||||
import { Navigate, useLocation } from 'react-router-dom';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: ReactNode;
|
||||
redirectTo?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected route wrapper that requires authentication
|
||||
* Redirects to sign-in page if user is not authenticated
|
||||
*/
|
||||
export function ProtectedRoute({
|
||||
children,
|
||||
redirectTo = '/sign-in'
|
||||
}: ProtectedRouteProps) {
|
||||
const { isLoaded, isSignedIn } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
// Wait for Clerk to load
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Redirect to sign-in if not authenticated
|
||||
if (!isSignedIn) {
|
||||
return <Navigate to={redirectTo} state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
43
src/components/auth/SignInPage.tsx
Normal file
43
src/components/auth/SignInPage.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { SignIn } from '@clerk/clerk-react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Sign-in page with Clerk authentication
|
||||
*/
|
||||
export function SignInPage() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
// Get the redirect path from location state, default to home
|
||||
const from = (location.state as any)?.from?.pathname || '/';
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Sign in to Beach Litter Mapping
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Join the community in tracking and cleaning coastal pollution
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<SignIn
|
||||
routing="path"
|
||||
path="/sign-in"
|
||||
signUpUrl="/sign-up"
|
||||
afterSignInUrl={from}
|
||||
appearance={{
|
||||
elements: {
|
||||
rootBox: 'w-full',
|
||||
card: 'shadow-none',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
src/components/auth/SignUpPage.tsx
Normal file
36
src/components/auth/SignUpPage.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SignUp } from '@clerk/clerk-react';
|
||||
|
||||
/**
|
||||
* Sign-up page with Clerk authentication
|
||||
*/
|
||||
export function SignUpPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Create your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Start reporting and tracking beach litter in your area
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
||||
<SignUp
|
||||
routing="path"
|
||||
path="/sign-up"
|
||||
signInUrl="/sign-in"
|
||||
afterSignUpUrl="/"
|
||||
appearance={{
|
||||
elements: {
|
||||
rootBox: 'w-full',
|
||||
card: 'shadow-none',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
58
src/components/auth/UserProfile.tsx
Normal file
58
src/components/auth/UserProfile.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useUser, useClerk } from '@clerk/clerk-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* User profile display component showing user info and sign-out option
|
||||
*/
|
||||
export function UserProfile() {
|
||||
const { user, isLoaded } = useUser();
|
||||
const { signOut } = useClerk();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await signOut();
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-10 w-10 bg-gray-300 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
{user.imageUrl && (
|
||||
<img
|
||||
src={user.imageUrl}
|
||||
alt={user.fullName || user.username || 'User'}
|
||||
className="h-10 w-10 rounded-full object-cover border-2 border-gray-200"
|
||||
/>
|
||||
)}
|
||||
<div className="hidden md:block">
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{user.fullName || user.username || 'User'}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{user.primaryEmailAddress?.emailAddress}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSignOut}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
src/components/auth/index.ts
Normal file
4
src/components/auth/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { ProtectedRoute } from './ProtectedRoute';
|
||||
export { UserProfile } from './UserProfile';
|
||||
export { SignInPage } from './SignInPage';
|
||||
export { SignUpPage } from './SignUpPage';
|
||||
Reference in New Issue
Block a user