nf
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ComponentType } from "react"
|
||||||
|
|
||||||
|
export interface IActionCardProps {
|
||||||
|
icon: ComponentType<{ size?: number; strokeWidth?: number }>
|
||||||
|
label: string
|
||||||
|
onClick: () => void
|
||||||
|
accent?: boolean
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.card {
|
||||||
|
@include mixins.reset-button;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
|
||||||
|
background: variables.$bg-default;
|
||||||
|
border: 1px solid variables.$border-default;
|
||||||
|
border-radius: variables.$radius-lg;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
|
||||||
|
color: variables.$text-secondary;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
transform 0.15s ease,
|
||||||
|
box-shadow 0.15s ease,
|
||||||
|
border-color 0.15s ease,
|
||||||
|
color 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
color: variables.$text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.accent {
|
||||||
|
background: variables.$purple-50;
|
||||||
|
border-color: variables.$purple-100;
|
||||||
|
color: variables.$purple-400;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: variables.$purple-100;
|
||||||
|
border-color: variables.$purple-400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@include typography.font-body-s;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { IActionCardProps } from "./ActionCard.d"
|
||||||
|
import type { JSX } from "react"
|
||||||
|
|
||||||
|
import { FunctionComponent } from "react"
|
||||||
|
|
||||||
|
import cs from "classnames"
|
||||||
|
|
||||||
|
import styles from "./ActionCard.module.scss"
|
||||||
|
|
||||||
|
export const ActionCard: FunctionComponent<IActionCardProps> = ({
|
||||||
|
icon: Icon,
|
||||||
|
label,
|
||||||
|
onClick,
|
||||||
|
accent = false,
|
||||||
|
className,
|
||||||
|
}): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cs(styles.card, accent && styles.accent, className)}
|
||||||
|
onClick={onClick}
|
||||||
|
data-testid="ActionCard"
|
||||||
|
>
|
||||||
|
<Icon size={32} strokeWidth={1.5} />
|
||||||
|
<span className={styles.label}>{label}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ActionCard"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ActionCard } from "./ActionCard"
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export interface IHomePageProps {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
@@ -1,36 +1,48 @@
|
|||||||
.homepage {
|
.root {
|
||||||
|
padding: 28px 24px 40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
gap: 32px;
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
color: #c7d0cc;
|
|
||||||
|
|
||||||
background: #000;
|
|
||||||
|
|
||||||
font-family: Roboto, sans-serif;
|
|
||||||
font-size: 2vw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.path {
|
.welcome {
|
||||||
font-style: italic;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.greeting {
|
||||||
font-size: 4vw;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
font-size: 52px;
|
||||||
|
line-height: 1.1;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: variables.$text-primary;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint {
|
.subtitle {
|
||||||
padding: 0.5rem;
|
@include typography.font-body-mr;
|
||||||
|
font-size: 18px;
|
||||||
pointer-events: none;
|
color: variables.$text-secondary;
|
||||||
|
margin: 0;
|
||||||
border: rgb(199 208 204 / 5%) 1px solid;
|
}
|
||||||
border-radius: 15px;
|
|
||||||
|
.actionsSection {
|
||||||
font-size: 1vw;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionsTitle {
|
||||||
|
@include typography.font-header-l;
|
||||||
|
color: variables.$text-primary;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,103 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useBreadcrumbs } from "@shared/context/BreadcrumbsContext"
|
import type { JSX } from "react"
|
||||||
import { Button } from "@shared/ui"
|
|
||||||
|
|
||||||
|
import { FunctionComponent, useMemo, useState } from "react"
|
||||||
|
|
||||||
|
import { FolderKanban, PlusIcon } from "lucide-react"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
|
||||||
|
import { ActionCard } from "@entities/dashboard"
|
||||||
|
import { CreateProjectModal } from "@features/project"
|
||||||
|
import api from "@shared/api"
|
||||||
|
import { useBreadcrumbs } from "@shared/context/BreadcrumbsContext"
|
||||||
|
import { useAppSelector } from "@shared/hooks/useAppSelector"
|
||||||
|
import { StaticLoader } from "@shared/ui/Loader"
|
||||||
|
import { RecentProjects } from "@widgets/Dashboard/RecentProjects"
|
||||||
|
import { StatsGrid } from "@widgets/Dashboard/StatsGrid"
|
||||||
|
|
||||||
|
import { IHomePageProps } from "./HomePage.d"
|
||||||
import cls from "./HomePage.module.scss"
|
import cls from "./HomePage.module.scss"
|
||||||
|
|
||||||
const HomePage = () => {
|
const RECENT_PROJECTS_LIMIT = 3
|
||||||
|
|
||||||
|
const WELCOME_SUBTITLES = [
|
||||||
|
"Управляйте своими проектами и отслеживайте их статус",
|
||||||
|
"Что будем делать сегодня?",
|
||||||
|
"Ваши проекты ждут вас",
|
||||||
|
"Создайте новый проект или продолжите работу над существующим",
|
||||||
|
"Всё под контролем — просматривайте и управляйте проектами",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const HomePage: FunctionComponent<IHomePageProps> = (): JSX.Element => {
|
||||||
useBreadcrumbs([{ label: "Главная" }])
|
useBreadcrumbs([{ label: "Главная" }])
|
||||||
|
const router = useRouter()
|
||||||
|
const user = useAppSelector((state) => state.user.user)
|
||||||
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
|
||||||
|
|
||||||
|
const { data: projects, isLoading, refetch } = api.useQuery("get", "/api/projects/")
|
||||||
|
|
||||||
|
const subtitle = useMemo(
|
||||||
|
() => WELCOME_SUBTITLES[Math.floor(Math.random() * WELCOME_SUBTITLES.length)],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
total: projects?.length ?? 0,
|
||||||
|
processing: projects?.filter((p) => p.status === "PROCESSING").length ?? 0,
|
||||||
|
done: projects?.filter((p) => p.status === "DONE").length ?? 0,
|
||||||
|
failed: projects?.filter((p) => p.status === "FAILED").length ?? 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentProjects = [...(projects ?? [])]
|
||||||
|
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
|
||||||
|
.slice(0, RECENT_PROJECTS_LIMIT)
|
||||||
|
|
||||||
|
const userName = user?.first_name || user?.username || "пользователь"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cls.homepage}>
|
<div className={cls.root}>
|
||||||
<p className={cls.title}>Coffee Project Starter</p>
|
{isLoading && <StaticLoader fullscreen />}
|
||||||
<pre className={cls.hint}>
|
|
||||||
Редактируйте <span className={cls.path}>src/pages/HomePage</span>{" "}
|
<div className={cls.welcome}>
|
||||||
чтобы начать разработку.
|
<h1 className={cls.greeting}>Добро пожаловать, {userName}</h1>
|
||||||
</pre>
|
<p className={cls.subtitle}>{subtitle}</p>
|
||||||
<Button variant="primary">Начать</Button>
|
</div>
|
||||||
|
|
||||||
|
<StatsGrid {...stats} />
|
||||||
|
|
||||||
|
<RecentProjects
|
||||||
|
projects={recentProjects}
|
||||||
|
isLoading={isLoading}
|
||||||
|
onProjectClick={(id) => router.push(`/projects/${id}`)}
|
||||||
|
onCreateClick={() => setIsCreateModalOpen(true)}
|
||||||
|
onViewAllClick={() => router.push("/projects")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={cls.actionsSection}>
|
||||||
|
<h2 className={cls.actionsTitle}>Быстрые действия</h2>
|
||||||
|
<div className={cls.actions}>
|
||||||
|
<ActionCard
|
||||||
|
icon={PlusIcon}
|
||||||
|
label="Создать проект"
|
||||||
|
accent
|
||||||
|
onClick={() => setIsCreateModalOpen(true)}
|
||||||
|
/>
|
||||||
|
<ActionCard
|
||||||
|
icon={FolderKanban}
|
||||||
|
label="Все проекты"
|
||||||
|
onClick={() => router.push("/projects")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CreateProjectModal
|
||||||
|
open={isCreateModalOpen}
|
||||||
|
onOpenChange={setIsCreateModalOpen}
|
||||||
|
onCreated={async () => {
|
||||||
|
await refetch()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { components } from "@shared/api/__generated__/openapi.types"
|
||||||
|
|
||||||
|
type ProjectRead = components["schemas"]["ProjectRead"]
|
||||||
|
|
||||||
|
export interface IRecentProjectsProps {
|
||||||
|
projects: ProjectRead[]
|
||||||
|
isLoading: boolean
|
||||||
|
onProjectClick: (id: string) => void
|
||||||
|
onCreateClick: () => void
|
||||||
|
onViewAllClick: () => void
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include typography.font-header-l;
|
||||||
|
color: variables.$text-primary;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 48px 0;
|
||||||
|
color: variables.$text-tertiary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyText {
|
||||||
|
@include typography.font-body-mr;
|
||||||
|
color: variables.$text-secondary;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import type { IRecentProjectsProps } from "./RecentProjects.d"
|
||||||
|
import type { JSX } from "react"
|
||||||
|
|
||||||
|
import { FunctionComponent } from "react"
|
||||||
|
|
||||||
|
import { FolderOpenIcon, PlusIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { ProjectCard } from "@entities/ProjectCard"
|
||||||
|
import { Button } from "@shared/ui"
|
||||||
|
|
||||||
|
import styles from "./RecentProjects.module.scss"
|
||||||
|
|
||||||
|
export const RecentProjects: FunctionComponent<IRecentProjectsProps> = ({
|
||||||
|
projects,
|
||||||
|
isLoading,
|
||||||
|
onProjectClick,
|
||||||
|
onCreateClick,
|
||||||
|
onViewAllClick,
|
||||||
|
}): JSX.Element => {
|
||||||
|
const isEmpty = !isLoading && projects.length === 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={styles.section} data-testid="RecentProjects">
|
||||||
|
<div className={styles.header}>
|
||||||
|
<h2 className={styles.title}>Последние проекты</h2>
|
||||||
|
{!isEmpty && (
|
||||||
|
<Button variant="ghost" size="sm" onClick={onViewAllClick}>
|
||||||
|
Все проекты
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isEmpty ? (
|
||||||
|
<div className={styles.empty}>
|
||||||
|
<FolderOpenIcon size={40} strokeWidth={1.5} />
|
||||||
|
<p className={styles.emptyText}>У вас пока нет проектов</p>
|
||||||
|
<Button variant="primary" onClick={onCreateClick}>
|
||||||
|
<PlusIcon /> Создать первый проект
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.grid}>
|
||||||
|
{projects.map((project) => (
|
||||||
|
<ProjectCard
|
||||||
|
key={project.id}
|
||||||
|
project={project}
|
||||||
|
progress={project.status === "PROCESSING" ? 45 : 0}
|
||||||
|
currentAction={project.status === "PROCESSING" ? "Обработка" : undefined}
|
||||||
|
onClick={() => onProjectClick(project.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./RecentProjects"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface IStatsGridProps {
|
||||||
|
total: number
|
||||||
|
processing: number
|
||||||
|
done: number
|
||||||
|
failed: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
background: variables.$bg-default;
|
||||||
|
border: 1px solid variables.$border-default;
|
||||||
|
border-radius: variables.$radius-md;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
@include typography.font-display;
|
||||||
|
color: variables.$text-primary;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@include typography.font-body-s;
|
||||||
|
color: variables.$text-secondary;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import type { IStatsGridProps } from "./StatsGrid.d"
|
||||||
|
import type { JSX } from "react"
|
||||||
|
|
||||||
|
import { FunctionComponent } from "react"
|
||||||
|
|
||||||
|
import styles from "./StatsGrid.module.scss"
|
||||||
|
|
||||||
|
interface IStatCardProps {
|
||||||
|
label: string
|
||||||
|
value: number
|
||||||
|
color?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatCard = ({ label, value, color }: IStatCardProps) => (
|
||||||
|
<div className={styles.card}>
|
||||||
|
<span className={styles.value} style={color ? { color } : undefined}>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
<span className={styles.label}>{label}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const StatsGrid: FunctionComponent<IStatsGridProps> = ({
|
||||||
|
total,
|
||||||
|
processing,
|
||||||
|
done,
|
||||||
|
failed,
|
||||||
|
}): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<div className={styles.grid} data-testid="StatsGrid">
|
||||||
|
<StatCard label="Всего проектов" value={total} />
|
||||||
|
<StatCard label="В процессе" value={processing} color="var(--color-warning)" />
|
||||||
|
<StatCard label="Готово" value={done} color="var(--color-success)" />
|
||||||
|
<StatCard label="Ошибка" value={failed} color="var(--color-danger)" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./StatsGrid"
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { JSX } from "react"
|
import type { JSX } from "react"
|
||||||
|
|
||||||
import { FolderKanban, Menu as MenuIcon } from "lucide-react"
|
import { FolderKanban, Home, Menu as MenuIcon } from "lucide-react"
|
||||||
import dynamic from "next/dynamic"
|
import dynamic from "next/dynamic"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { FunctionComponent, useState } from "react"
|
import { FunctionComponent, useState } from "react"
|
||||||
@@ -63,6 +63,11 @@ export const Header: FunctionComponent<IHeaderProps> = (): JSX.Element => {
|
|||||||
open={isDrawerOpen}
|
open={isDrawerOpen}
|
||||||
onClose={() => setIsDrawerOpen(false)}
|
onClose={() => setIsDrawerOpen(false)}
|
||||||
buttons={[
|
buttons={[
|
||||||
|
{
|
||||||
|
label: "Главная",
|
||||||
|
icon: Home,
|
||||||
|
path: "/",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Проекты",
|
label: "Проекты",
|
||||||
icon: FolderKanban,
|
icon: FolderKanban,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { FunctionComponent, useCallback, useEffect, useRef, useState } from "react"
|
import { FunctionComponent, useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
|
||||||
|
import { Tooltip } from "@radix-ui/themes"
|
||||||
|
|
||||||
import { DeleteFileModal } from "@features/project"
|
import { DeleteFileModal } from "@features/project"
|
||||||
import {
|
import {
|
||||||
useDeleteArtifact,
|
useDeleteArtifact,
|
||||||
@@ -272,12 +274,11 @@ export const FileTree: FunctionComponent<IFileTreeProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon size={16} className={styles.fileIcon} />
|
<Icon size={16} className={styles.fileIcon} />
|
||||||
<span
|
<Tooltip delayDuration={1500} content={file.displayName}>
|
||||||
className={styles.fileName}
|
<span className={styles.fileName}>
|
||||||
title={file.displayName}
|
|
||||||
>
|
|
||||||
{file.displayName}
|
{file.displayName}
|
||||||
</span>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={styles.removeButton}
|
className={styles.removeButton}
|
||||||
@@ -371,12 +372,11 @@ export const FileTree: FunctionComponent<IFileTreeProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon size={16} className={styles.fileIcon} />
|
<Icon size={16} className={styles.fileIcon} />
|
||||||
<span
|
<Tooltip delayDuration={1500} content={file.original_filename}>
|
||||||
className={styles.fileName}
|
<span className={styles.fileName}>
|
||||||
title={file.original_filename}
|
|
||||||
>
|
|
||||||
{file.original_filename}
|
{file.original_filename}
|
||||||
</span>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
{!used && (
|
{!used && (
|
||||||
<button
|
<button
|
||||||
@@ -466,12 +466,11 @@ export const FileTree: FunctionComponent<IFileTreeProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon size={16} className={styles.fileIcon} />
|
<Icon size={16} className={styles.fileIcon} />
|
||||||
<span
|
<Tooltip delayDuration={1500} content={displayName}>
|
||||||
className={styles.fileName}
|
<span className={styles.fileName}>
|
||||||
title={displayName}
|
|
||||||
>
|
|
||||||
{displayName}
|
{displayName}
|
||||||
</span>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
{!used && (
|
{!used && (
|
||||||
<button
|
<button
|
||||||
@@ -551,12 +550,11 @@ export const FileTree: FunctionComponent<IFileTreeProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Icon size={16} className={styles.fileIcon} />
|
<Icon size={16} className={styles.fileIcon} />
|
||||||
<span
|
<Tooltip delayDuration={1500} content={displayName}>
|
||||||
className={styles.fileName}
|
<span className={styles.fileName}>
|
||||||
title={displayName}
|
|
||||||
>
|
|
||||||
{displayName}
|
{displayName}
|
||||||
</span>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</button>
|
</button>
|
||||||
{!used && (
|
{!used && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user