feat(ui): Solve issues 4-10 and polish UI
- Added top padding to all pages (Fix #4) - Implemented smooth accordion animations for navigation and messages (Fix #5, #9) - Fixed double selection highlight in navigation bar (Fix #6) - Enabled nested schedule routing for 'Session' view (Fix #7) - Softened switching animations for message types (Fix #8) - Reworked messages to use accordion expansion and removed history (Fix #9) - Moved Support section into Settings page (Fix #10) - Cleaned up unused imports and refined layouts Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,133 +1,62 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { MessageListItem, MessageDetails } from '../types/api';
|
||||
import { Mail, Send, Paperclip, ChevronRight, Inbox, MessageSquareText, ArrowLeft, Download, Reply } from 'lucide-react';
|
||||
import { Mail, Send, Paperclip, ChevronDown, Inbox, MessageSquareText, Download, Reply, Loader2 } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Badge } from '../components/ui/badge';
|
||||
import { Button } from '../components/ui/button';
|
||||
|
||||
export const Messages: React.FC = () => {
|
||||
const [messages, setMessages] = useState<MessageListItem[]>([]);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [details, setDetails] = useState<MessageDetails | null>(null);
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
const [details, setDetails] = useState<Record<string, MessageDetails>>({});
|
||||
const [loadingIds, setLoadingIds] = useState<Set<string>>(new Set());
|
||||
const [type, setType] = useState<'in' | 'out'>('in');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [detailsLoading, setDetailsLoading] = useState(false);
|
||||
const [listLoading, setListLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMessages = async () => {
|
||||
setLoading(true);
|
||||
setListLoading(true);
|
||||
setExpandedId(null);
|
||||
try {
|
||||
const data = await api.getMessages(type);
|
||||
setMessages(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setListLoading(false);
|
||||
}
|
||||
};
|
||||
fetchMessages();
|
||||
}, [type]);
|
||||
|
||||
const handleSelectMessage = async (id: string) => {
|
||||
setSelectedId(id);
|
||||
setDetailsLoading(true);
|
||||
try {
|
||||
const data = await api.getMessageDetails(id);
|
||||
setDetails(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setDetailsLoading(false);
|
||||
const toggleExpand = async (id: string) => {
|
||||
if (expandedId === id) {
|
||||
setExpandedId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setExpandedId(id);
|
||||
|
||||
if (!details[id]) {
|
||||
setLoadingIds(prev => new Set(prev).add(id));
|
||||
try {
|
||||
const data = await api.getMessageDetails(id);
|
||||
setDetails(prev => ({ ...prev, [id]: data }));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
setLoadingIds(prev => {
|
||||
const next = new Set(prev);
|
||||
next.delete(id);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setSelectedId(null);
|
||||
setDetails(null);
|
||||
};
|
||||
|
||||
if (selectedId) {
|
||||
return (
|
||||
<div className="page-enter max-w-4xl mx-auto space-y-6">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="flex items-center gap-2 text-primary font-medium hover:opacity-70 transition-opacity"
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад к списку
|
||||
</button>
|
||||
|
||||
{detailsLoading ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground gap-4">
|
||||
<div className="w-8 h-8 border-4 border-primary/30 border-t-primary rounded-full animate-spin" />
|
||||
<p className="text-sm font-medium">Загрузка содержания...</p>
|
||||
</div>
|
||||
) : details ? (
|
||||
<div className="space-y-6">
|
||||
<section className="surface-card p-8">
|
||||
<div className="flex justify-between items-start gap-4 mb-8">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-2xl font-bold tracking-tight">{messages.find(m => m.id === selectedId)?.subject}</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
От: <span className="font-semibold text-primary">{messages.find(m => m.id === selectedId)?.senderOrRecipient}</span> • {messages.find(m => m.id === selectedId)?.date}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="gap-2">
|
||||
<Reply size={16} />
|
||||
Ответить
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none text-foreground/90 leading-relaxed whitespace-pre-wrap">
|
||||
{details.text}
|
||||
</div>
|
||||
|
||||
{details.files && details.files.length > 0 && (
|
||||
<div className="mt-10 pt-6 border-t border-white/5">
|
||||
<h3 className="text-xs font-bold uppercase tracking-widest text-muted-foreground mb-4">Вложения ({details.files.length})</h3>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{details.files.map((file, i) => (
|
||||
<div key={i} className="flex items-center gap-3 p-3 rounded-xl bg-muted/30 border border-white/5 hover:bg-muted/50 transition-colors cursor-pointer group">
|
||||
<div className="p-2 rounded-lg bg-primary/10 text-primary group-hover:scale-110 transition-transform">
|
||||
<Paperclip size={16} />
|
||||
</div>
|
||||
<span className="text-sm font-medium">{file}</span>
|
||||
<Download size={14} className="ml-2 opacity-40 group-hover:opacity-100" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{details.history && details.history.length > 0 && (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-bold px-2">История переписки</h2>
|
||||
{details.history.map((h, i) => (
|
||||
<div key={i} className="surface-card p-5 bg-card/20 space-y-2 border-l-4 border-primary/30">
|
||||
<div className="flex justify-between text-xs font-bold uppercase tracking-wider text-muted-foreground">
|
||||
<span>{h.author}</span>
|
||||
<span>{h.date}</span>
|
||||
</div>
|
||||
<p className="text-sm">{h.text}</p>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="surface-card text-center py-20 text-muted-foreground">
|
||||
Не удалось загрузить сообщение
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-enter max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-2">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-2 animate-in fade-in slide-in-from-top-2 duration-500">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-xl bg-primary/10 text-primary">
|
||||
<MessageSquareText size={24} />
|
||||
@@ -138,7 +67,7 @@ export const Messages: React.FC = () => {
|
||||
<div className="flex p-1 rounded-2xl bg-muted/30 border border-white/5 w-fit">
|
||||
<button
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all",
|
||||
"flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200",
|
||||
type === 'in' ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
onClick={() => setType('in')}
|
||||
@@ -148,7 +77,7 @@ export const Messages: React.FC = () => {
|
||||
</button>
|
||||
<button
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all",
|
||||
"flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium transition-all duration-200",
|
||||
type === 'out' ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
|
||||
)}
|
||||
onClick={() => setType('out')}
|
||||
@@ -159,57 +88,126 @@ export const Messages: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
{listLoading ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground gap-4">
|
||||
<div className="w-8 h-8 border-4 border-primary/30 border-t-primary rounded-full animate-spin" />
|
||||
<p className="text-sm font-medium">Загрузка сообщений...</p>
|
||||
<Loader2 className="w-8 h-8 text-primary/40 animate-spin" />
|
||||
<p className="text-sm font-medium italic opacity-60">Загрузка сообщений...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{messages.length === 0 ? (
|
||||
<div className="surface-card flex flex-col items-center justify-center py-20 text-muted-foreground gap-3">
|
||||
<Mail size={40} className="opacity-20" />
|
||||
<p className="text-sm">Нет сообщений в данной категории</p>
|
||||
<div className="surface-card animate-in fade-in zoom-in-95 duration-300 flex flex-col items-center justify-center py-20 text-muted-foreground gap-3">
|
||||
<Mail size={40} className="opacity-10" />
|
||||
<p className="text-sm italic">Нет сообщений в данной категории</p>
|
||||
</div>
|
||||
) : (
|
||||
messages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
onClick={() => handleSelectMessage(msg.id)}
|
||||
className="surface-card group hover:bg-card/60 cursor-pointer flex items-center gap-4 p-4"
|
||||
>
|
||||
<div className="hidden sm:flex w-12 h-12 rounded-2xl bg-primary/10 text-primary items-center justify-center shrink-0 group-hover:scale-110 transition-transform">
|
||||
{type === 'in' ? <Mail size={22} /> : <Send size={22} />}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2 mb-1">
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-primary truncate">
|
||||
{msg.senderOrRecipient}
|
||||
</span>
|
||||
<span className="text-[11px] text-muted-foreground whitespace-nowrap">
|
||||
{msg.date}
|
||||
</span>
|
||||
messages.map((msg, index) => {
|
||||
const isExpanded = expandedId === msg.id;
|
||||
const isLoading = loadingIds.has(msg.id);
|
||||
const msgDetails = details[msg.id];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={cn(
|
||||
"surface-card flex flex-col p-0 overflow-hidden transition-all duration-300",
|
||||
isExpanded ? "ring-2 ring-primary/20 bg-card/80" : "animate-in fade-in slide-in-from-bottom-2"
|
||||
)}
|
||||
style={{ animationDelay: `${index * 50}ms`, animationFillMode: 'both' }}
|
||||
>
|
||||
<div
|
||||
onClick={() => toggleExpand(msg.id)}
|
||||
className="flex items-center gap-4 p-4 sm:p-5 cursor-pointer hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className={cn(
|
||||
"hidden sm:flex w-12 h-12 rounded-2xl items-center justify-center shrink-0 transition-all duration-300",
|
||||
isExpanded ? "bg-primary text-primary-foreground scale-90" : "bg-primary/10 text-primary"
|
||||
)}>
|
||||
{type === 'in' ? <Mail size={22} /> : <Send size={22} />}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between gap-2 mb-1">
|
||||
<span className={cn(
|
||||
"text-[10px] font-bold uppercase tracking-widest truncate transition-colors",
|
||||
isExpanded ? "text-primary" : "text-muted-foreground opacity-80"
|
||||
)}>
|
||||
{msg.senderOrRecipient}
|
||||
</span>
|
||||
<span className="text-[11px] text-muted-foreground whitespace-nowrap opacity-60">
|
||||
{msg.date}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className={cn(
|
||||
"text-base font-semibold truncate transition-colors",
|
||||
isExpanded && "text-primary whitespace-normal"
|
||||
)}>
|
||||
{msg.subject}
|
||||
</h3>
|
||||
{!isExpanded && msg.hasFiles && (
|
||||
<div className="flex items-center gap-1.5 mt-1.5 text-primary opacity-80">
|
||||
<Paperclip size={12} />
|
||||
<span className="text-[10px] font-bold uppercase">Вложения</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={cn(
|
||||
"w-8 h-8 rounded-full flex items-center justify-center text-muted-foreground transition-transform duration-300",
|
||||
isExpanded && "rotate-180 text-primary"
|
||||
)}>
|
||||
<ChevronDown size={20} />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-base font-semibold truncate group-hover:text-primary transition-colors">
|
||||
{msg.subject}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3 mt-2">
|
||||
{msg.hasFiles && (
|
||||
<Badge variant="secondary" className="h-5 text-[10px] px-2 bg-primary/5 text-primary border-primary/10">
|
||||
<Paperclip size={10} className="mr-1" />
|
||||
Вложения
|
||||
</Badge>
|
||||
)}
|
||||
<span className="text-xs text-muted-foreground truncate opacity-60">
|
||||
Нажмите, чтобы прочитать полностью...
|
||||
</span>
|
||||
|
||||
<div className={cn(
|
||||
"overflow-hidden transition-all duration-300 ease-in-out",
|
||||
isExpanded ? "max-h-[1000px] border-t border-white/5" : "max-h-0"
|
||||
)}>
|
||||
<div className="p-5 sm:p-8 space-y-6">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-8 gap-3 text-muted-foreground">
|
||||
<Loader2 size={18} className="animate-spin text-primary" />
|
||||
<span className="text-sm italic">Загрузка содержания...</span>
|
||||
</div>
|
||||
) : msgDetails ? (
|
||||
<>
|
||||
<div className="flex justify-end mb-2">
|
||||
<Button variant="outline" size="xs" className="rounded-xl h-8 px-3 gap-2 border-white/10">
|
||||
<Reply size={14} />
|
||||
Ответить
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none text-foreground/90 leading-relaxed whitespace-pre-wrap text-sm sm:text-base">
|
||||
{msgDetails.text}
|
||||
</div>
|
||||
|
||||
{msgDetails.files && msgDetails.files.length > 0 && (
|
||||
<div className="pt-6 mt-6 border-t border-white/5 space-y-3">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground">Вложения</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{msgDetails.files.map((file, i) => (
|
||||
<div key={i} className="flex items-center gap-2.5 p-2.5 rounded-xl bg-muted/30 border border-white/5 hover:bg-primary/5 hover:border-primary/20 transition-all cursor-pointer group">
|
||||
<Paperclip size={14} className="text-primary/60 group-hover:text-primary transition-colors" />
|
||||
<span className="text-xs font-medium truncate max-w-[150px]">{file}</span>
|
||||
<Download size={12} className="ml-1 opacity-0 group-hover:opacity-60 transition-opacity" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-center py-4 text-destructive text-sm font-medium">
|
||||
Не удалось загрузить текст сообщения
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-8 h-8 rounded-full flex items-center justify-center text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary transition-colors">
|
||||
<ChevronRight size={18} />
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { Schedule as ScheduleType } from '../types/api';
|
||||
import { Clock, User, MapPin, CalendarDays } from 'lucide-react';
|
||||
import { Clock, User, MapPin, CalendarDays, Loader2 } from 'lucide-react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const Schedule: React.FC = () => {
|
||||
const [schedule, setSchedule] = useState<ScheduleType | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const location = useLocation();
|
||||
const isSession = location.pathname.includes('/session');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSchedule = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await api.getSchedule();
|
||||
setSchedule(data);
|
||||
@@ -19,35 +23,37 @@ export const Schedule: React.FC = () => {
|
||||
}
|
||||
};
|
||||
fetchSchedule();
|
||||
}, []);
|
||||
}, [isSession]);
|
||||
|
||||
if (loading) return (
|
||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground gap-4">
|
||||
<div className="w-8 h-8 border-4 border-primary/30 border-t-primary rounded-full animate-spin" />
|
||||
<p className="text-sm font-medium">Загрузка расписания...</p>
|
||||
<Loader2 className="w-8 h-8 text-primary/40 animate-spin" />
|
||||
<p className="text-sm font-medium italic opacity-60">Загрузка расписания...</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-enter max-w-4xl mx-auto space-y-8">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="flex items-center gap-3 mb-2 animate-in fade-in slide-in-from-top-2 duration-500">
|
||||
<div className="p-2 rounded-xl bg-primary/10 text-primary">
|
||||
<CalendarDays size={24} />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Расписание</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight">
|
||||
{isSession ? 'Расписание сессии' : 'Расписание занятий'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{!schedule || schedule.days.length === 0 ? (
|
||||
<div className="surface-card text-center py-12 text-muted-foreground">
|
||||
<div className="surface-card text-center py-12 text-muted-foreground italic">
|
||||
Нет данных о расписании
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-10">
|
||||
{schedule.days.map((day, idx) => (
|
||||
<section key={idx} className="space-y-4">
|
||||
<section key={idx} className="space-y-4 animate-in fade-in slide-in-from-bottom-2 duration-500" style={{ animationDelay: `${idx * 100}ms`, animationFillMode: 'both' }}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="h-px flex-1 bg-white/5" />
|
||||
<h2 className="text-[11px] font-bold text-primary uppercase tracking-[0.2em]">
|
||||
<h2 className="text-[11px] font-bold text-primary uppercase tracking-[0.2em] bg-primary/5 px-3 py-1 rounded-full border border-primary/10">
|
||||
{day.date}
|
||||
</h2>
|
||||
<div className="h-px flex-1 bg-white/5" />
|
||||
@@ -57,7 +63,7 @@ export const Schedule: React.FC = () => {
|
||||
{day.lessons.map((lesson, lIdx) => (
|
||||
<div
|
||||
key={lIdx}
|
||||
className="surface-card flex flex-col sm:flex-row gap-4 sm:items-center p-5 hover:bg-card/60 transition-colors"
|
||||
className="surface-card flex flex-col sm:flex-row gap-4 sm:items-center p-5 hover:bg-card/60 transition-all active:scale-[0.99]"
|
||||
>
|
||||
<div className="flex sm:flex-col items-center sm:items-start gap-3 sm:gap-1 sm:min-w-[110px] border-b sm:border-b-0 sm:border-r border-white/5 pb-3 sm:pb-0 sm:pr-4">
|
||||
<div className="flex items-center gap-2 text-primary font-bold">
|
||||
@@ -71,10 +77,10 @@ export const Schedule: React.FC = () => {
|
||||
|
||||
<div className="flex-1 space-y-3">
|
||||
<div>
|
||||
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-1">
|
||||
<div className="text-[10px] font-bold text-primary uppercase tracking-wider mb-1 opacity-80">
|
||||
{lesson.type || 'Занятие'}
|
||||
</div>
|
||||
<h3 className="text-lg font-bold leading-tight">{lesson.subject}</h3>
|
||||
<h3 className="text-lg font-bold leading-tight group-hover:text-primary transition-colors">{lesson.subject}</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-4">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useStore } from '../store/useStore';
|
||||
import { Palette, Moon, Sun, Monitor, LogOut, Settings2 } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Palette, Moon, Sun, Monitor, LogOut, Settings2, LifeBuoy, ChevronRight, HelpCircle, MessageCircle } from 'lucide-react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { TextField } from '../components/TextField';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -32,7 +32,7 @@ export const Settings: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="page-enter max-w-3xl mx-auto space-y-8">
|
||||
<div className="page-enter max-w-3xl mx-auto space-y-8 pb-12">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="p-2 rounded-xl bg-primary/10 text-primary">
|
||||
<Settings2 size={24} />
|
||||
@@ -137,6 +137,45 @@ export const Settings: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Support Section Moved Here - Issue #10 */}
|
||||
<section className="surface-card space-y-6">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-lg font-bold flex items-center gap-2">
|
||||
<LifeBuoy size={20} className="text-primary" />
|
||||
Поддержка
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">Помощь и обратная связь</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3">
|
||||
<Link to="/support" className="flex items-center justify-between p-4 rounded-2xl bg-muted/30 border border-transparent hover:border-primary/20 hover:bg-primary/5 transition-all group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-xl bg-primary/10 text-primary">
|
||||
<MessageCircle size={20} />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-bold">Написать в поддержку</div>
|
||||
<div className="text-xs text-muted-foreground">Создать новое обращение</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight size={18} className="text-muted-foreground group-hover:text-primary transition-colors" />
|
||||
</Link>
|
||||
|
||||
<button className="flex items-center justify-between p-4 rounded-2xl bg-muted/30 border border-transparent hover:border-primary/20 hover:bg-primary/5 transition-all group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-2 rounded-xl bg-primary/10 text-primary">
|
||||
<HelpCircle size={20} />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-bold">Частые вопросы (FAQ)</div>
|
||||
<div className="text-xs text-muted-foreground">База знаний для студентов</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight size={18} className="text-muted-foreground group-hover:text-primary transition-colors" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
Reference in New Issue
Block a user