feat: add hero banner (#463)

This commit is contained in:
Manuel
2024-05-12 18:19:01 +02:00
committed by GitHub
parent 1570faa20a
commit 87fe03dd00
9 changed files with 269 additions and 51 deletions

View File

@@ -0,0 +1,37 @@
.bannerContainer {
padding: 3rem;
border-radius: 8px;
overflow: hidden;
background: linear-gradient(
130deg,
#fa52521f 0%,
var(--mantine-color-dark-6) 35%,
var(--mantine-color-dark-6) 100%
) !important;
}
.scrollContainer {
height: 100%;
transform: rotateZ(10deg);
}
@keyframes scrolling {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
.scrollAnimationContainer {
animation: scrolling;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@media (prefers-reduced-motion: reduce) {
.scrollAnimationContainer {
animation: none !important;
}
}

View File

@@ -0,0 +1,96 @@
import { Box, Grid, GridCol, Group, Image, Stack, Title } from "@mantine/core";
import { splitToNChunks } from "@homarr/common";
import classes from "./hero-banner.module.css";
const icons = [
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/homarr.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/sabnzbd.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/deluge.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/radarr.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/sonarr.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/lidarr.svg",
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/pihole.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dashdot.png",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/overseerr.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/plex.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/jellyfin.svg",
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/homeassistant.svg",
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/freshrss.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/readarr.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/transmission.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/qbittorrent.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/nzbget.png",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/openmediavault.svg",
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/docker.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/jellyseerr.svg",
"https://cdn.jsdelivr.net/gh/loganmarchione/homelab-svg-assets/assets/adguardhome.svg",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/tdarr.png",
"https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/svg/prowlarr.svg",
];
const countIconGroups = 3;
const animationDurationInSeconds = 12;
export const HeroBanner = () => {
const arrayInChunks = splitToNChunks(icons, countIconGroups);
const gridSpan = 12 / countIconGroups;
return (
<Box className={classes.bannerContainer} bg="dark.6" pos="relative">
<Stack gap={0}>
<Title order={2} c="dimmed">
Welcome back to your
</Title>
<Group gap="xs">
<Image src="/logo/logo.png" w={40} h={40} />
<Title>Homarr Dashboard</Title>
</Group>
</Stack>
<Box
className={classes.scrollContainer}
w={"30%"}
top={0}
right={0}
pos="absolute"
>
<Grid>
{Array(countIconGroups)
.fill(0)
.map((_, columnIndex) => (
<GridCol key={`grid-column-${columnIndex}`} span={gridSpan}>
<Stack
className={classes.scrollAnimationContainer}
style={{
animationDuration: `${animationDurationInSeconds - columnIndex}s`,
}}
>
{arrayInChunks[columnIndex]?.map((icon, index) => (
<Image
key={`grid-column-${columnIndex}-scroll-1-${index}`}
src={icon}
radius="md"
w={50}
h={50}
/>
))}
{/* This is used for making the animation seem seamless */}
{arrayInChunks[columnIndex]?.map((icon, index) => (
<Image
key={`grid-column-${columnIndex}-scroll-2-${index}`}
src={icon}
radius="md"
w={50}
h={50}
/>
))}
</Stack>
</GridCol>
))}
</Grid>
</Box>
</Box>
);
};

View File

@@ -1,8 +1,18 @@
import { Title } from "@mantine/core";
import Link from "next/link";
import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react";
import { api } from "@homarr/api/server";
import { getScopedI18n } from "@homarr/translation/server";
import { Test } from "./test";
import { HeroBanner } from "./_components/hero-banner";
interface LinkProps {
title: string;
subtitle: string;
count: number;
href: string;
}
export async function generateMetadata() {
const t = await getScopedI18n("management");
@@ -14,20 +24,76 @@ export async function generateMetadata() {
}
export default async function ManagementPage() {
const t = await getScopedI18n("management.title");
const dateNow = new Date();
const timeOfDay =
dateNow.getHours() < 10
? "morning"
: dateNow.getHours() < 17
? "afternoon"
: "evening";
const statistics = await api.home.getStats();
const t = await getScopedI18n("management.page.home");
const links: LinkProps[] = [
{
count: statistics.countBoards,
href: "/manage/boards",
subtitle: t("statisticLabel.boards"),
title: t("statistic.countBoards"),
},
{
count: statistics.countUsers,
href: "/manage/boards",
subtitle: t("statisticLabel.authentication"),
title: t("statistic.createUser"),
},
{
count: statistics.countInvites,
href: "/manage/boards",
subtitle: t("statisticLabel.authentication"),
title: t("statistic.createInvite"),
},
{
count: statistics.countIntegrations,
href: "/manage/integrations",
subtitle: t("statisticLabel.resources"),
title: t("statistic.addIntegration"),
},
{
count: statistics.countApps,
href: "/manage/apps",
subtitle: t("statisticLabel.resources"),
title: t("statistic.addApp"),
},
{
count: statistics.countGroups,
href: "/manage/users/groups",
subtitle: t("statisticLabel.authorization"),
title: t("statistic.manageRoles"),
},
];
return (
<>
<Title>{t(timeOfDay, { username: "admin" })}</Title>
<Test />
<HeroBanner />
<Space h="md" />
<SimpleGrid cols={{ xs: 1, sm: 2, md: 3 }}>
{links.map((link, index) => (
<Card
component={Link}
href={link.href}
key={`link-${index}`}
withBorder
>
<Group justify="space-between">
<Group>
<Text size="2.4rem" fw="bolder">
{link.count}
</Text>
<Stack gap={0}>
<Text c="red" size="xs">
{link.subtitle}
</Text>
<Text fw="bold">{link.title}</Text>
</Stack>
</Group>
<IconArrowRight />
</Group>
</Card>
))}
</SimpleGrid>
</>
);
}

View File

@@ -1,38 +0,0 @@
"use client";
import { useCallback, useState } from "react";
import type { ChangeEvent } from "react";
import { Button, Stack, Text, TextInput } from "@mantine/core";
import { clientApi } from "@homarr/api/client";
export const Test = () => {
const [value, setValue] = useState("");
const [message, setMessage] = useState<string>("Hello, world!");
const { mutate } = clientApi.user.setMessage.useMutation();
clientApi.user.test.useSubscription(undefined, {
onData({ message }) {
setMessage(message);
},
onError(err) {
alert(err);
},
});
const onChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => setValue(event.target.value),
[setValue],
);
const onClick = useCallback(() => {
mutate(value);
setValue("");
}, [mutate, value]);
return (
<Stack>
<TextInput label="Update message" value={value} onChange={onChange} />
<Button onClick={onClick}>Update message</Button>
<Text>This message gets through subscription: {message}</Text>
</Stack>
);
};