feat: all project setup
This commit is contained in:
115
app/(auth)/auth-complete/page.tsx
Normal file
115
app/(auth)/auth-complete/page.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
// ============================================================================
|
||||
// Auth Complete Page
|
||||
// ============================================================================
|
||||
|
||||
import React, { useEffect, useState, Suspense } from "react";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { useAuth } from "@/components/auth/auth-provider";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { AlertCircle, CheckCircle, Loader2 } from "lucide-react";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
function AuthCompleteContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const { login } = useAuth();
|
||||
const [status, setStatus] = useState<"loading" | "success" | "error">(
|
||||
"loading"
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const token = searchParams.get("token");
|
||||
|
||||
if (!token) {
|
||||
setStatus("error");
|
||||
setError("Токен авторизации не найден");
|
||||
return;
|
||||
}
|
||||
|
||||
const completeAuth = async () => {
|
||||
try {
|
||||
await login(token);
|
||||
setStatus("success");
|
||||
|
||||
// Redirect after short delay
|
||||
setTimeout(() => {
|
||||
router.push("/");
|
||||
}, 1500);
|
||||
} catch (err: any) {
|
||||
setStatus("error");
|
||||
setError(err?.error?.message || "Ошибка авторизации");
|
||||
}
|
||||
};
|
||||
|
||||
completeAuth();
|
||||
}, [searchParams, login, router]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-950 dark:to-slate-900 p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mx-auto mb-4">
|
||||
{status === "loading" && (
|
||||
<Loader2 className="h-12 w-12 animate-spin text-primary" />
|
||||
)}
|
||||
{status === "success" && (
|
||||
<CheckCircle className="h-12 w-12 text-green-500" />
|
||||
)}
|
||||
{status === "error" && (
|
||||
<AlertCircle className="h-12 w-12 text-destructive" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<CardTitle>
|
||||
{status === "loading" && "Авторизация..."}
|
||||
{status === "success" && "Успешно!"}
|
||||
{status === "error" && "Ошибка"}
|
||||
</CardTitle>
|
||||
|
||||
<CardDescription>
|
||||
{status === "loading" && "Пожалуйста, подождите"}
|
||||
{status === "success" && "Перенаправляем в приложение..."}
|
||||
{status === "error" && error}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
{status === "error" && (
|
||||
<CardContent>
|
||||
<Alert variant="destructive" className="mb-4">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Button onClick={() => router.push("/login")} className="w-full">
|
||||
Вернуться к входу
|
||||
</Button>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AuthCompletePage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<Loader2 className="h-12 w-12 animate-spin text-primary" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AuthCompleteContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
135
app/(auth)/login/page.tsx
Normal file
135
app/(auth)/login/page.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
|
||||
// ============================================================================
|
||||
// Login Page
|
||||
// ============================================================================
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useAuth } from "@/components/auth/auth-provider";
|
||||
import { USE_MOCKS, BOT_USERNAME } from "@/lib/utils/constants";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AlertCircle, LogIn, Loader2 } from "lucide-react";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
|
||||
export default function LoginPage() {
|
||||
const { loginMock, error: authError } = useAuth();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleTelegramLogin = () => {
|
||||
const telegramUrl = `https://t.me/${BOT_USERNAME}?start=login`;
|
||||
window.open(telegramUrl, "_blank");
|
||||
};
|
||||
|
||||
const handleMockLogin = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
await loginMock();
|
||||
} catch (err: any) {
|
||||
setError(err?.error?.message || "Ошибка входа");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const displayError = error || authError;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-950 dark:to-slate-900 p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="text-center space-y-2">
|
||||
<div className="mx-auto w-12 h-12 bg-primary rounded-lg flex items-center justify-center mb-2">
|
||||
<span className="text-2xl font-bold text-primary-foreground">
|
||||
TG
|
||||
</span>
|
||||
</div>
|
||||
<CardTitle className="text-2xl">TGEX</CardTitle>
|
||||
<CardDescription>
|
||||
Система управления рекламными закупками в Telegram
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
{displayError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{displayError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{USE_MOCKS && (
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={handleMockLogin}
|
||||
disabled={loading}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Вход...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<LogIn className="mr-2 h-4 w-4" />
|
||||
Войти как тестовый пользователь
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">
|
||||
или
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleTelegramLogin}
|
||||
variant={USE_MOCKS ? "outline" : "default"}
|
||||
className="w-full"
|
||||
size="lg"
|
||||
>
|
||||
<svg
|
||||
className="mr-2 h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm5.562 8.161c-.18.717-.962 3.767-1.362 5.001-.169.523-.506.697-.831.715-.708.064-1.245-.468-1.931-.918-.988-.646-1.547-1.048-2.508-1.678-.111-.073-.336-.218-.325-.409.009-.169.169-.316.373-.503l3.067-2.854c.234-.217.136-.359-.15-.223l-3.845 2.365c-.516.324-.989.472-1.411.461-.675-.018-1.969-.393-2.937-.719-.688-.232-1.236-.354-1.188-.755.025-.211.325-.426.9-.645 4.089-1.781 6.785-2.954 8.09-3.518 3.853-1.636 4.65-1.918 5.17-1.926.115-.002.371.026.536.161.141.114.18.267.198.375.019.108.042.353.024.545z" />
|
||||
</svg>
|
||||
Войти через Telegram
|
||||
</Button>
|
||||
|
||||
{USE_MOCKS && (
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription className="text-xs">
|
||||
<strong>Режим разработки:</strong> Используются моки данных.
|
||||
Бекенд не требуется.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-center text-muted-foreground mt-4">
|
||||
Войдя в систему, вы соглашаетесь с условиями использования
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user