This repository has been archived on 2024-10-29. You can view files and clone it, but cannot push or open issues or pull requests.
website/components/Application/Application.tsx

248 lines
6.8 KiB
TypeScript

import { useState, useEffect } from "react"
import Image from "next/image"
import { PlusIcon, MenuIcon, UsersIcon, XIcon } from "@heroicons/react/solid"
import classNames from "clsx"
import { useMediaQuery } from "react-responsive"
import { useSwipeable } from "react-swipeable"
import type { DirectionSidebar } from "./Sidebar"
import { Sidebar } from "./Sidebar"
import { IconButton } from "../design/IconButton"
import { IconLink } from "../design/IconLink"
import { Guilds } from "./Guilds/Guilds"
import { Divider } from "../design/Divider"
import { Members } from "./Members"
import { useAuthentication } from "../../tools/authentication"
export interface ChannelsPath {
channelId: number
}
export interface GuildsPath {
guildId: number
}
export interface GuildsChannelsPath extends GuildsPath, ChannelsPath {}
const isGuildsChannelsPath = (path: any): path is GuildsChannelsPath => {
return path.guildId !== undefined && path.channelId !== undefined
}
export type ApplicationPath =
| "/application"
| "/application/guilds/join"
| "/application/guilds/create"
| `/application/users/${number}`
| `/application/users/settings`
| GuildsChannelsPath
| GuildsPath
export interface ApplicationProps {
path: ApplicationPath
guildLeftSidebar?: React.ReactNode
title: string
}
export const Application: React.FC<
React.PropsWithChildren<ApplicationProps>
> = (props) => {
const { children, path, guildLeftSidebar, title } = props
const { user } = useAuthentication()
const [mounted, setMounted] = useState(false)
const isMobile = useMediaQuery({
query: "(max-width: 900px)",
})
const [visibleSidebars, setVisibleSidebars] = useState({
left: !isMobile,
right: false,
})
const handleToggleSidebars = (direction: DirectionSidebar): void => {
if (!isMobile) {
if (direction === "left") {
return setVisibleSidebars({
...visibleSidebars,
left: !visibleSidebars.left,
})
}
if (direction === "right") {
return setVisibleSidebars({
...visibleSidebars,
right: !visibleSidebars.right,
})
}
} else {
if (direction === "right" && visibleSidebars.left) {
return setVisibleSidebars({
left: false,
right: true,
})
}
if (direction === "left" && visibleSidebars.right) {
return setVisibleSidebars({
left: true,
right: false,
})
}
if (direction === "left" && !visibleSidebars.right) {
return setVisibleSidebars({
...visibleSidebars,
left: !visibleSidebars.left,
})
}
if (direction === "right" && !visibleSidebars.left) {
return setVisibleSidebars({
...visibleSidebars,
right: !visibleSidebars.right,
})
}
}
}
const handleCloseSidebars = (): void => {
if (isMobile && (visibleSidebars.left || visibleSidebars.right)) {
return setVisibleSidebars({
left: false,
right: false,
})
}
}
const swipeableHandlers = useSwipeable({
trackMouse: false,
trackTouch: true,
preventScrollOnSwipe: true,
onSwipedRight: () => {
if (visibleSidebars.right) {
return setVisibleSidebars({ ...visibleSidebars, right: false })
}
setVisibleSidebars({
...visibleSidebars,
left: true,
})
},
onSwipedLeft: () => {
if (isGuildsChannelsPath(path)) {
if (visibleSidebars.left) {
return setVisibleSidebars({ ...visibleSidebars, left: false })
}
setVisibleSidebars({
...visibleSidebars,
right: true,
})
}
},
})
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
<>
<header className="z-50 flex h-16 items-center justify-between bg-gray-200 px-2 py-3 shadow-lg dark:bg-gray-800">
<IconButton
className="h-10 w-10 p-2"
onClick={() => {
return handleToggleSidebars("left")
}}
>
{!visibleSidebars.left ? <MenuIcon /> : <XIcon />}
</IconButton>
<div
data-cy="application-title"
className="text-md font-semibold text-green-800 dark:text-green-400"
>
{title}
</div>
<div className="flex space-x-2">
{title.startsWith("#") && (
<IconButton
data-cy="icon-button-right-sidebar-members"
className="h-10 w-10 p-2"
onClick={() => {
return handleToggleSidebars("right")
}}
>
{!visibleSidebars.right ? <UsersIcon /> : <XIcon />}
</IconButton>
)}
</div>
</header>
<main
className="h-full-without-header relative flex overflow-hidden"
{...swipeableHandlers}
>
<Sidebar
direction="left"
visible={visibleSidebars.left}
isMobile={isMobile}
>
<div className="left-0 top-0 z-50 flex min-w-[92px] flex-col space-y-4 border-r-2 border-gray-500 bg-gray-200 py-2 dark:border-white/20 dark:bg-gray-800">
<IconLink
href={`/application/users/settings`}
selected={path === `/application/users/settings`}
title="Settings"
>
<Image
quality={100}
className="rounded-full"
src={
user.logo == null
? "/images/data/user-default.png"
: user.logo
}
alt={"Users's profil picture"}
draggable={false}
width={48}
height={48}
/>
</IconLink>
<IconLink
href="/application"
selected={path === "/application"}
title="Join or create a Guild"
>
<PlusIcon className="h-12 w-12 text-green-800 dark:text-green-400" />
</IconLink>
<Divider />
<Guilds path={path} />
</div>
{guildLeftSidebar}
</Sidebar>
<div
id="application-page-content"
onClick={handleCloseSidebars}
className={classNames(
"h-full-without-header relative top-0 z-0 flex w-full flex-1 flex-col overflow-y-auto transition",
{
"absolute opacity-20":
isMobile && (visibleSidebars.left || visibleSidebars.right),
},
)}
>
{children}
</div>
{isGuildsChannelsPath(path) && (
<Sidebar
direction="right"
visible={visibleSidebars.right}
isMobile={isMobile}
>
<Members />
</Sidebar>
)}
</main>
</>
)
}