Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cbc6935db |
@@ -1,79 +1,15 @@
|
|||||||
'use client';
|
import { Suspense } from 'react';
|
||||||
|
import LoginForm from '@/components/login/LoginForm';
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
import { loginAction, LoginResult, clearExpiredSessionAction } from '@/lib/actions/auth';
|
export default function LoginPage({
|
||||||
import { useActionState, useEffect } from 'react';
|
searchParams
|
||||||
import { useSearchParams } from 'next/navigation';
|
}: {
|
||||||
|
searchParams: Promise<{ expired: boolean | undefined }>;
|
||||||
const initialState: LoginResult = {
|
}) {
|
||||||
message: '',
|
|
||||||
success: true
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const expired = searchParams.get('expired') === 'true';
|
|
||||||
const [state, formAction] = useActionState(loginAction, initialState);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (expired) {
|
|
||||||
clearExpiredSessionAction();
|
|
||||||
}
|
|
||||||
}, [expired]);
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-gray-900 px-4">
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<div className="w-full max-w-md space-y-8 rounded-lg bg-gray-800 p-8 shadow-lg">
|
<LoginForm searchParams={searchParams} />
|
||||||
<div className="text-center">
|
</Suspense>
|
||||||
<h1 className="text-3xl font-bold text-white">ACC Server Manager</h1>
|
|
||||||
<p className="mt-2 text-gray-400">Sign in to manage your servers</p>
|
|
||||||
</div>
|
|
||||||
{expired && (
|
|
||||||
<div className="rounded-md border border-yellow-700 bg-yellow-900/50 p-3 text-sm text-yellow-200">
|
|
||||||
Your session has expired. Please sign in again.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{state?.success ? null : (
|
|
||||||
<div className="rounded-md border border-red-700 bg-red-900/50 p-3 text-sm text-red-200">
|
|
||||||
{state?.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form action={formAction} className="space-y-6">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="username" className="mb-2 block text-sm font-medium text-gray-300">
|
|
||||||
Username
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="username"
|
|
||||||
name="username"
|
|
||||||
type="text"
|
|
||||||
autoComplete="username"
|
|
||||||
required
|
|
||||||
className="form-input w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label htmlFor="password" className="mb-2 block text-sm font-medium text-gray-300">
|
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
autoComplete="current-password"
|
|
||||||
required
|
|
||||||
className="form-input w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="w-full rounded-md bg-blue-600 px-4 py-3 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-none"
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
82
src/components/login/LoginForm.tsx
Normal file
82
src/components/login/LoginForm.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { clearExpiredSessionAction, loginAction, LoginResult } from '@/lib/actions/auth';
|
||||||
|
import { use, useActionState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const initialState: LoginResult = {
|
||||||
|
message: '',
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LoginForm({
|
||||||
|
searchParams
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<{ expired: boolean | undefined }>;
|
||||||
|
}) {
|
||||||
|
const params = use(searchParams);
|
||||||
|
const expired = params.expired;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (expired) {
|
||||||
|
clearExpiredSessionAction();
|
||||||
|
}
|
||||||
|
}, [expired]);
|
||||||
|
const [state, formAction] = useActionState(loginAction, initialState);
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center bg-gray-900 px-4">
|
||||||
|
<div className="w-full max-w-md space-y-8 rounded-lg bg-gray-800 p-8 shadow-lg">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-3xl font-bold text-white">ACC Server Manager</h1>
|
||||||
|
<p className="mt-2 text-gray-400">Sign in to manage your servers</p>
|
||||||
|
</div>
|
||||||
|
{expired && (
|
||||||
|
<div className="rounded-md border border-yellow-700 bg-yellow-900/50 p-3 text-sm text-yellow-200">
|
||||||
|
Your session has expired. Please sign in again.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{state?.success ? null : (
|
||||||
|
<div className="rounded-md border border-red-700 bg-red-900/50 p-3 text-sm text-red-200">
|
||||||
|
{state?.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form action={formAction} className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="username" className="mb-2 block text-sm font-medium text-gray-300">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
type="text"
|
||||||
|
autoComplete="username"
|
||||||
|
required
|
||||||
|
className="form-input w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="mb-2 block text-sm font-medium text-gray-300">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
required
|
||||||
|
className="form-input w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full rounded-md bg-blue-600 px-4 py-3 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 focus:outline-none"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -49,7 +49,7 @@ export async function fetchClientAPI<T>(
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login?expired=true';
|
||||||
return { error: 'unauthorized' };
|
return { error: 'unauthorized' };
|
||||||
}
|
}
|
||||||
throw new Error(`API Error: ${response.statusText} - ${method} - ${BASE_URL}${endpoint}`);
|
throw new Error(`API Error: ${response.statusText} - ${method} - ${BASE_URL}${endpoint}`);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export async function requireAuth(skipRedirect?: boolean) {
|
|||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
|
|
||||||
if (!skipRedirect && (!session.token || !session.user)) {
|
if (!skipRedirect && (!session.token || !session.user)) {
|
||||||
redirect('/login');
|
redirect('/login?expired=true');
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
|
|||||||
Reference in New Issue
Block a user