Files
2026-01-21 22:43:54 +03:00

189 lines
6.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
// ============================================================================
// Onboarding Page - First Workspace Creation
// ============================================================================
import { useState, useRef } from "react";
import { useRouter } from "next/navigation";
import { Building2, Loader2, Megaphone, Upload, Trash2, X } from "lucide-react";
import { workspacesApi } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { STORAGE_KEYS } from "@/lib/utils/constants";
export default function OnboardingPage() {
const router = useRouter();
const [name, setName] = useState("");
const [avatarFile, setAvatarFile] = useState<File | null>(null);
const [avatarPreview, setAvatarPreview] = useState<string | null>(null);
const [isCreating, setIsCreating] = useState(false);
const [error, setError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleAvatarChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
setError("Размер файла не должен превышать 2 МБ");
return;
}
setAvatarFile(file);
const reader = new FileReader();
reader.onload = () => {
setAvatarPreview(reader.result as string);
};
reader.readAsDataURL(file);
setError(null);
};
const handleAvatarRemove = () => {
setAvatarFile(null);
setAvatarPreview(null);
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
};
const handleCreate = async () => {
if (!name.trim()) return;
try {
setIsCreating(true);
setError(null);
const workspace = await workspacesApi.create({ name: name.trim(), avatar_file: avatarFile });
// Save as selected workspace
localStorage.setItem(STORAGE_KEYS.SELECTED_WORKSPACE, workspace.id);
// Redirect to dashboard
router.replace(`/dashboard/${workspace.id}`);
} catch (err: any) {
setError(err?.error?.message || "Не удалось создать воркспейс");
} finally {
setIsCreating(false);
}
};
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-background to-muted p-4">
<Card className="w-full max-w-md">
<CardHeader className="text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
<Megaphone className="h-8 w-8 text-primary" />
</div>
<CardTitle className="text-2xl">Добро пожаловать в Smartpost!</CardTitle>
<CardDescription>
Создайте свой первый воркспейс для начала работы
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Avatar */}
<div className="flex items-center gap-4">
<Avatar className="h-16 w-16">
<AvatarImage src={avatarPreview || undefined} alt="Avatar preview" />
<AvatarFallback className="text-xl">
{name.charAt(0).toUpperCase() || "W"}
</AvatarFallback>
</Avatar>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => fileInputRef.current?.click()}
disabled={isCreating}
>
<Upload className="h-4 w-4 mr-2" />
Загрузить
</Button>
{avatarPreview && (
<Button
variant="outline"
size="sm"
onClick={handleAvatarRemove}
disabled={isCreating}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
<p className="text-xs text-muted-foreground">
PNG, JPG или GIF. Максимум 2 МБ.
</p>
<input
ref={fileInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleAvatarChange}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="workspace-name">Название воркспейса</Label>
<Input
id="workspace-name"
placeholder="Например: Моя команда"
value={name}
onChange={(e) => setName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleCreate();
}
}}
disabled={isCreating}
/>
<p className="text-xs text-muted-foreground">
Воркспейс это изолированное пространство для вашей команды. Вы
сможете пригласить участников и настроить права доступа.
</p>
</div>
{error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive">
{error}
</div>
)}
</CardContent>
<CardFooter>
<Button
className="w-full"
onClick={handleCreate}
disabled={!name.trim() || isCreating}
>
{isCreating ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Создание...
</>
) : (
<>
<Building2 className="mr-2 h-4 w-4" />
Создать воркспейс
</>
)}
</Button>
</CardFooter>
</Card>
</div>
);
}