mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-05-25 01:12:42 -04:00
Updates
This commit is contained in:
@@ -8,7 +8,7 @@ generator client {
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = "file:./dev.db"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Account {
|
||||
@@ -26,6 +26,10 @@ model Account {
|
||||
session_state String?
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
displayName String
|
||||
avatarUrl String?
|
||||
bio String?
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@ import { authConfig } from '@/auth';
|
||||
// Initialize NextAuth with the configuration
|
||||
export const {
|
||||
handlers: { GET, POST },
|
||||
auth,
|
||||
signIn,
|
||||
signOut,
|
||||
} = NextAuth({
|
||||
...authConfig,
|
||||
// Enable debug logs in development
|
||||
@@ -25,4 +22,4 @@ export const {
|
||||
},
|
||||
});
|
||||
|
||||
export { GET, POST, auth, signIn, signOut };
|
||||
export { GET, POST};
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { signOut } from '@/app/api/auth/[...nextauth]/route';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import {signOut, useSession} from "next-auth/react";
|
||||
import {useRouter} from 'next/navigation';
|
||||
import {useEffect} from 'react';
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { data: session, status } = useSession();
|
||||
console.log('DashboardPage');
|
||||
const {data: session, status} = useSession();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -16,6 +16,7 @@ export default function DashboardPage() {
|
||||
}, [status, router]);
|
||||
|
||||
if (status === 'loading') {
|
||||
console.log("LOADING");
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500"></div>
|
||||
@@ -23,7 +24,9 @@ export default function DashboardPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
console.log(status, session);
|
||||
|
||||
if (status === 'unauthenticated' || !session) {
|
||||
// router.push('/login');
|
||||
return null;
|
||||
}
|
||||
@@ -41,7 +44,7 @@ export default function DashboardPage() {
|
||||
Welcome, {session.user?.name || 'User'}!
|
||||
</span>
|
||||
<button
|
||||
onClick={() => signOut({ redirect: true, callbackUrl: '/' })}
|
||||
onClick={() => signOut({redirect: true, callbackUrl: '/'})}
|
||||
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Sign out
|
||||
@@ -56,15 +59,17 @@ export default function DashboardPage() {
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">Dashboard</h2>
|
||||
|
||||
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{/* User Info Card */}
|
||||
<div className="bg-white overflow-hidden shadow rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 bg-indigo-500 rounded-md p-3">
|
||||
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
@@ -89,8 +94,10 @@ export default function DashboardPage() {
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 bg-green-500 rounded-md p-3">
|
||||
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
@@ -110,13 +117,17 @@ export default function DashboardPage() {
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 bg-yellow-500 rounded-md p-3">
|
||||
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg className="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-5 w-0 flex-1">
|
||||
<dl>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">Last Login</dt>
|
||||
<dt className="text-sm font-medium text-gray-500 truncate">Last
|
||||
Login
|
||||
</dt>
|
||||
<dd className="flex items-baseline">
|
||||
<div className="text-2xl font-semibold text-gray-900">
|
||||
Just now
|
||||
@@ -137,14 +148,18 @@ export default function DashboardPage() {
|
||||
<li className="px-6 py-4">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center">
|
||||
<svg className="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
<div
|
||||
className="h-10 w-10 rounded-full bg-indigo-100 flex items-center justify-center">
|
||||
<svg className="h-6 w-6 text-indigo-600" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round"
|
||||
strokeWidth={2} d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-900">Successfully logged in</p>
|
||||
<p className="text-sm font-medium text-gray-900">Successfully logged
|
||||
in</p>
|
||||
<p className="text-sm text-gray-500">A few seconds ago</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,286 +1,282 @@
|
||||
'use client';
|
||||
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {signIn} from 'next-auth/react';
|
||||
import {useRouter, useSearchParams} from 'next/navigation';
|
||||
import {useState, useEffect} from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
|
||||
const errorParam = searchParams.get('error');
|
||||
const registered = searchParams.get('registered');
|
||||
|
||||
// Handle URL parameters and errors
|
||||
useEffect(() => {
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
|
||||
// Handle error parameter
|
||||
if (errorParam) {
|
||||
let errorMessage = 'An error occurred during login';
|
||||
|
||||
// Map common error codes to user-friendly messages
|
||||
if (errorParam === 'CredentialsSignin') {
|
||||
errorMessage = 'Invalid email or password';
|
||||
} else if (errorParam === 'OAuthAccountNotLinked') {
|
||||
errorMessage = 'This email is already associated with another account';
|
||||
} else if (errorParam === 'OAuthCallbackError') {
|
||||
errorMessage = 'An error occurred during social sign in';
|
||||
} else if (errorParam === 'AccessDenied') {
|
||||
errorMessage = 'You do not have permission to sign in';
|
||||
} else if (errorParam === 'Verification') {
|
||||
errorMessage = 'Account not verified. Please check your email.';
|
||||
}
|
||||
|
||||
setError(errorMessage);
|
||||
|
||||
// Clean up the URL
|
||||
newSearchParams.delete('error');
|
||||
router.replace(`/login?${newSearchParams.toString()}`);
|
||||
}
|
||||
|
||||
// Show success message if user was just registered
|
||||
if (registered) {
|
||||
setSuccess('Registration successful! Please sign in with your credentials.');
|
||||
newSearchParams.delete('registered');
|
||||
router.replace(`/login?${newSearchParams.toString()}`);
|
||||
}
|
||||
}, [errorParam, registered, router, searchParams]);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard';
|
||||
const errorParam = searchParams.get('error');
|
||||
const registered = searchParams.get('registered');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setSuccess('');
|
||||
setIsLoading(true);
|
||||
// Handle URL parameters and errors
|
||||
useEffect(() => {
|
||||
const newSearchParams = new URLSearchParams(searchParams);
|
||||
|
||||
try {
|
||||
// Validate inputs
|
||||
if (!email || !password) {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
// Handle error parameter
|
||||
if (errorParam) {
|
||||
let errorMessage = 'An error occurred during login';
|
||||
|
||||
// Basic email validation
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
throw new Error('Please enter a valid email address');
|
||||
}
|
||||
// Map common error codes to user-friendly messages
|
||||
if (errorParam === 'CredentialsSignin') {
|
||||
errorMessage = 'Invalid email or password';
|
||||
} else if (errorParam === 'OAuthAccountNotLinked') {
|
||||
errorMessage = 'This email is already associated with another account';
|
||||
} else if (errorParam === 'OAuthCallbackError') {
|
||||
errorMessage = 'An error occurred during social sign in';
|
||||
} else if (errorParam === 'AccessDenied') {
|
||||
errorMessage = 'You do not have permission to sign in';
|
||||
} else if (errorParam === 'Verification') {
|
||||
errorMessage = 'Account not verified. Please check your email.';
|
||||
}
|
||||
|
||||
// Attempt to sign in
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email: email.trim(),
|
||||
password,
|
||||
callbackUrl,
|
||||
});
|
||||
setError(errorMessage);
|
||||
|
||||
// Handle the result
|
||||
if (result?.error) {
|
||||
// Handle specific error messages
|
||||
let errorMessage = 'Invalid email or password';
|
||||
if (result.error === 'CredentialsSignin') {
|
||||
errorMessage = 'Invalid email or password';
|
||||
} else if (result.error === 'AccessDenied') {
|
||||
errorMessage = 'You do not have permission to sign in';
|
||||
} else if (result.error === 'Configuration') {
|
||||
errorMessage = 'Server configuration error';
|
||||
} else if (result.error === 'Verification') {
|
||||
errorMessage = 'Account not verified. Please check your email.';
|
||||
} else if (result.error === 'OAuthSignin') {
|
||||
errorMessage = 'Error in OAuth sign in. Please try again.';
|
||||
} else if (result.error === 'OAuthCallback') {
|
||||
errorMessage = 'Error in OAuth callback. Please try again.';
|
||||
} else if (result.error === 'OAuthCreateAccount') {
|
||||
errorMessage = 'Error creating OAuth account. Please try again.';
|
||||
} else if (result.error === 'EmailCreateAccount') {
|
||||
errorMessage = 'Error creating account. Please try again.';
|
||||
} else if (result.error === 'Callback') {
|
||||
errorMessage = 'Error in authentication callback. Please try again.';
|
||||
} else if (result.error === 'OAuthAccountNotLinked') {
|
||||
errorMessage = 'This email is already associated with another account';
|
||||
// Clean up the URL
|
||||
newSearchParams.delete('error');
|
||||
router.replace(`/login?${newSearchParams.toString()}`);
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// If we got here, sign in was successful
|
||||
if (result?.url) {
|
||||
// Ensure we're using the correct callback URL
|
||||
const url = new URL(result.url);
|
||||
const callbackUrlParam = url.searchParams.get('callbackUrl');
|
||||
const finalUrl = callbackUrlParam || callbackUrl;
|
||||
|
||||
// Force a full page reload to ensure all session data is loaded
|
||||
window.location.href = finalUrl;
|
||||
} else {
|
||||
// Fallback in case result.url is not available
|
||||
window.location.href = callbackUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
// Show success message if user was just registered
|
||||
if (registered) {
|
||||
setSuccess('Registration successful! Please sign in with your credentials.');
|
||||
newSearchParams.delete('registered');
|
||||
router.replace(`/login?${newSearchParams.toString()}`);
|
||||
}
|
||||
}, [errorParam, registered, router, searchParams]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Don't have an account?{' '}
|
||||
<Link
|
||||
href={`/signup${callbackUrl ? `?callbackUrl=${encodeURIComponent(callbackUrl)}` : ''}`}
|
||||
className="font-medium text-indigo-600 hover:text-indigo-500"
|
||||
>
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white text-gray-500">Or continue with</span>
|
||||
</div>
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setSuccess('');
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Validate inputs
|
||||
if (!email || !password) {
|
||||
throw new Error('Email and password are required');
|
||||
}
|
||||
|
||||
// Basic email validation
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
throw new Error('Please enter a valid email address');
|
||||
}
|
||||
|
||||
// Attempt to sign in
|
||||
const result = await signIn('credentials', {
|
||||
redirect: false,
|
||||
email: email.trim(),
|
||||
password,
|
||||
callbackUrl,
|
||||
});
|
||||
|
||||
// Handle the result
|
||||
if (result?.error) {
|
||||
// Handle specific error messages
|
||||
let errorMessage = 'Invalid email or password';
|
||||
if (result.error === 'CredentialsSignin') {
|
||||
errorMessage = 'Invalid email or password';
|
||||
} else if (result.error === 'AccessDenied') {
|
||||
errorMessage = 'You do not have permission to sign in';
|
||||
} else if (result.error === 'Configuration') {
|
||||
errorMessage = 'Server configuration error';
|
||||
} else if (result.error === 'Verification') {
|
||||
errorMessage = 'Account not verified. Please check your email.';
|
||||
} else if (result.error === 'OAuthSignin') {
|
||||
errorMessage = 'Error in OAuth sign in. Please try again.';
|
||||
} else if (result.error === 'OAuthCallback') {
|
||||
errorMessage = 'Error in OAuth callback. Please try again.';
|
||||
} else if (result.error === 'OAuthCreateAccount') {
|
||||
errorMessage = 'Error creating OAuth account. Please try again.';
|
||||
} else if (result.error === 'EmailCreateAccount') {
|
||||
errorMessage = 'Error creating account. Please try again.';
|
||||
} else if (result.error === 'Callback') {
|
||||
errorMessage = 'Error in authentication callback. Please try again.';
|
||||
} else if (result.error === 'OAuthAccountNotLinked') {
|
||||
errorMessage = 'This email is already associated with another account';
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// If we got here, sign in was successful
|
||||
if (result?.url) {
|
||||
// Ensure we're using the correct callback URL
|
||||
const url = new URL(result.url);
|
||||
const callbackUrlParam = url.searchParams.get('callbackUrl');
|
||||
const finalUrl = callbackUrlParam || callbackUrl;
|
||||
|
||||
// Force a full page reload to ensure all session data is loaded
|
||||
window.location.href = finalUrl;
|
||||
} else {
|
||||
// Fallback in case result.url is not available
|
||||
window.location.href = callbackUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
<div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p className="mt-2 text-center text-sm text-gray-600">
|
||||
Don't have an account?{' '}
|
||||
<Link
|
||||
href={`/signup${callbackUrl ? `?callbackUrl=${encodeURIComponent(callbackUrl)}` : ''}`}
|
||||
className="font-medium text-indigo-600 hover:text-indigo-500"
|
||||
>
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white text-gray-500">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-center">
|
||||
<div className="w-full max-w-xs">
|
||||
<div>
|
||||
<button
|
||||
onClick={() => signIn('google', {callbackUrl})}
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
<span className="sr-only">Sign in with Google</span>
|
||||
<svg className="w-5 h-5" aria-hidden="true" fill="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{success && (
|
||||
<div className="bg-green-50 border-l-4 border-green-500 p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-green-700">{success}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clipRule="evenodd"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-red-700">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<input type="hidden" name="remember" value="true"/>
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label htmlFor="email-address" className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email-address"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
name="remember-me"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="remember-me"
|
||||
className="ml-2 block text-sm text-gray-900"
|
||||
>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="text-sm">
|
||||
<a
|
||||
href="#"
|
||||
className="font-medium text-indigo-600 hover:text-indigo-500"
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<button
|
||||
onClick={() => signIn('google', { callbackUrl })}
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
<span className="sr-only">Sign in with Google</span>
|
||||
<svg className="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
onClick={() => signIn('github', { callbackUrl })}
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
<span className="sr-only">Sign in with GitHub</span>
|
||||
<svg className="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.699 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C17.14 18.25 20 14.42 20 10.017 20 4.484 15.522 0 10 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{success && (
|
||||
<div className="bg-green-50 border-l-4 border-green-500 p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-green-700">{success}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-red-700">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<input type="hidden" name="remember" value="true" />
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label htmlFor="email-address" className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email-address"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
name="remember-me"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label
|
||||
htmlFor="remember-me"
|
||||
className="ml-2 block text-sm text-gray-900"
|
||||
>
|
||||
Remember me
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="text-sm">
|
||||
<a
|
||||
href="#"
|
||||
className="font-medium text-indigo-600 hover:text-indigo-500"
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,17 +6,17 @@ export default function Home() {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-extrabold text-gray-900 sm:text-5xl sm:tracking-tight lg:text-6xl">
|
||||
Welcome to BayesBond
|
||||
BayesBond
|
||||
</h1>
|
||||
<p className="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
|
||||
A modern web application with authentication and more.
|
||||
A bonding platform for rational thinkers
|
||||
</p>
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/signup"
|
||||
className="px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10 transition-colors"
|
||||
>
|
||||
Get Started
|
||||
Sign Up
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
@@ -26,41 +26,6 @@ export default function Home() {
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16">
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-8">Features</h2>
|
||||
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="text-indigo-600 mb-4">
|
||||
<svg className="h-8 w-8" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Fast</h3>
|
||||
<p className="text-gray-600">Built with Next.js for optimal performance and SEO.</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="text-indigo-600 mb-4">
|
||||
<svg className="h-8 w-8" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Secure</h3>
|
||||
<p className="text-gray-600">Built-in authentication with NextAuth.js.</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="text-indigo-600 mb-4">
|
||||
<svg className="h-8 w-8" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M11 17a1 1 0 001.447.894l4-2A1 1 0 0017 15V9.236a1 1 0 00-1.447-.894l-4 2a1 1 0 00-.553.894V17zM15.211 6.276a1 1 0 000-1.788l-4.764-2.382a1 1 0 00-.894 0L4.789 4.488a1 1 0 000 1.788l4.764 2.382a1 1 0 00.894 0l4.764-2.382zM4.447 8.342A1 1 0 003 9.236V15a1 1 0 00.553.894l4 2A1 1 0 009 17v-5.764a1 1 0 00-.553-.894l-4-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Modern</h3>
|
||||
<p className="text-gray-600">Built with the latest web technologies.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -110,6 +110,70 @@ export default function SignupPage() {
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label htmlFor="name" className="sr-only">
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Full Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email-address" className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email-address"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
|
||||
isLoading ? 'opacity-70 cursor-not-allowed' : ''
|
||||
}`}
|
||||
>
|
||||
{isLoading ? 'Creating account...' : 'Sign up'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
@@ -121,8 +185,8 @@ export default function SignupPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<div className="mt-6 flex justify-center">
|
||||
<div className="w-full max-w-xs">
|
||||
<button
|
||||
onClick={() => signIn('google', { callbackUrl })}
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
@@ -133,18 +197,6 @@ export default function SignupPage() {
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
onClick={() => signIn('github', { callbackUrl })}
|
||||
className="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
<span className="sr-only">Sign in with GitHub</span>
|
||||
<svg className="w-5 h-5" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.699 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C17.14 18.25 20 14.42 20 10.017 20 4.484 15.522 0 10 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,69 +223,7 @@ export default function SignupPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label htmlFor="name" className="sr-only">
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Full Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email-address" className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email-address"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 ${
|
||||
isLoading ? 'opacity-70 cursor-not-allowed' : ''
|
||||
}`}
|
||||
>
|
||||
{isLoading ? 'Creating account...' : 'Sign up'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
||||
import { NextAuthOptions } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import { prisma } from "./prisma";
|
||||
import { compare } from 'bcryptjs';
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
adapter: PrismaAdapter(prisma),
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
signOut: "/",
|
||||
error: "/login",
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: "Credentials",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "email" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) {
|
||||
throw new Error("Please enter your email and password");
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: {
|
||||
email: credentials.email,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user || !user.password) {
|
||||
throw new Error("No user found with this email");
|
||||
}
|
||||
|
||||
const isValid = await compare(credentials.password, user.password);
|
||||
|
||||
if (!isValid) {
|
||||
throw new Error("Incorrect password");
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
if (session?.user) {
|
||||
session.user.id = token.id as string;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
debug: process.env.NODE_ENV === "development",
|
||||
} as const;
|
||||
@@ -5,7 +5,7 @@ declare global {
|
||||
var prisma: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
||||
const globalForPrisma = global as unknown as { prisma: PrismaClient }
|
||||
|
||||
export const prisma = globalForPrisma.prisma || new PrismaClient();
|
||||
|
||||
|
||||
@@ -23,5 +23,6 @@
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"exclude": ["node_modules"],
|
||||
"sourceMap": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user