feat: skeletons for most ui components

This commit is contained in:
diced
2024-12-16 14:40:41 -08:00
parent 2af85b4fcf
commit d1cef2c56d
13 changed files with 283 additions and 82 deletions

View File

@@ -19,7 +19,7 @@ import fileIcon from './fileIcon';
function PlaceholderContent({ text, Icon }: { text: string; Icon: Icon }) {
return (
<Stack align='center'>
<Icon size={48} />
<Icon size='4rem' stroke={2} style={{ filter: 'drop-shadow(0 0 10px rgba(0, 0, 0, 0.9))' }} />
<Text size='md' ta='center'>
{text}
</Text>
@@ -126,7 +126,11 @@ export default function DashboardFileType({
transform: 'translate(-50%, -50%)',
}}
>
<IconPlayerPlay size='4rem' stroke={3} />
<IconPlayerPlay
size='4rem'
stroke={3}
style={{ filter: 'drop-shadow(0 0 10px rgba(0, 0, 0, 0.9))' }}
/>
</Center>
</Box>
) : (

View File

@@ -3,7 +3,7 @@ import Stat from '@/components/Stat';
import type { Response } from '@/lib/api/response';
import { bytes } from '@/lib/bytes';
import useLogin from '@/lib/hooks/useLogin';
import { LoadingOverlay, Paper, ScrollArea, SimpleGrid, Table, Text, Title } from '@mantine/core';
import { Paper, ScrollArea, SimpleGrid, Skeleton, Table, Text, Title } from '@mantine/core';
import { IconDeviceSdCard, IconEyeFilled, IconFiles, IconLink, IconStarFilled } from '@tabler/icons-react';
import useSWR from 'swr';
@@ -17,9 +17,13 @@ export default function DashboardHome() {
<Title order={1}>
Welcome back, <b>{user?.username}</b>
</Title>
<Text size='sm' c='dimmed'>
You have <b>{statsLoading ? '...' : stats?.filesUploaded}</b> files uploaded.
</Text>
<Skeleton visible={statsLoading} animate>
<Text size='sm' c='dimmed'>
You have <b>{statsLoading ? '...' : stats?.filesUploaded}</b> files uploaded.
</Text>
</Skeleton>
{user?.quota && (user.quota.maxBytes || user.quota.maxFiles) ? (
<Text size='sm' c='dimmed'>
{user.quota.filesQuota === 'BY_BYTES' ? (
@@ -47,9 +51,11 @@ export default function DashboardHome() {
</Title>
{recentLoading ? (
<Paper withBorder p='md' radius='md' pos='relative' h={300}>
<LoadingOverlay visible />
</Paper>
<SimpleGrid cols={{ base: 1, md: 2, lg: 3 }} spacing={{ base: 'sm', md: 'md' }}>
{[...Array(3)].map((i) => (
<Skeleton key={i} height={350} />
))}
</SimpleGrid>
) : recent?.length !== 0 ? (
<SimpleGrid cols={{ base: 1, md: 2, lg: 3 }} spacing={{ base: 'sm', md: 'md' }}>
{recent!.map((file) => (
@@ -70,9 +76,46 @@ export default function DashboardHome() {
</Text>
{statsLoading ? (
<Paper withBorder p='md' radius='md' pos='relative' h={300}>
<LoadingOverlay visible />
</Paper>
<>
<SimpleGrid cols={{ base: 1, md: 2, lg: 4 }} spacing={{ base: 'sm', md: 'md' }}>
{[...Array(8)].map((i) => (
<Skeleton key={i} height={105} />
))}
</SimpleGrid>
<Title order={3} mt='lg' mb='xs'>
File types
</Title>
<Paper radius='sm' withBorder>
<ScrollArea.Autosize mah={400} type='auto'>
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>File Type</Table.Th>
<Table.Th>Count</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{[1, 2, 3, 4, 5].map((i) => (
<Table.Tr key={i}>
<Table.Td>
<Skeleton animate>
<Text>...</Text>
</Skeleton>
</Table.Td>
<Table.Td>
<Skeleton animate>
<Text>...</Text>
</Skeleton>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea.Autosize>
</Paper>
</>
) : (
<>
<SimpleGrid cols={{ base: 1, md: 2, lg: 4 }} spacing={{ base: 'sm', md: 'md' }}>

View File

@@ -1,15 +1,5 @@
import DashboardFile from '@/components/file/DashboardFile';
import {
Button,
Center,
Group,
LoadingOverlay,
Pagination,
Paper,
SimpleGrid,
Stack,
Title,
} from '@mantine/core';
import { Button, Center, Group, Pagination, Paper, SimpleGrid, Skeleton, Stack, Title } from '@mantine/core';
import { IconFileUpload, IconFilesOff } from '@tabler/icons-react';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -51,9 +41,7 @@ export default function Files({ id }: { id?: string }) {
pos='relative'
>
{isLoading ? (
<Paper withBorder h={200}>
<LoadingOverlay visible />
</Paper>
[...Array(9)].map((_, i) => <Skeleton key={i} height={350} animate />)
) : (data?.page?.length ?? 0 > 0) ? (
data?.page.map((file) => <DashboardFile key={file.id} file={file} />)
) : (

View File

@@ -1,6 +1,6 @@
import { Response } from '@/lib/api/response';
import { Folder } from '@/lib/db/models/folder';
import { Center, Group, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Center, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconLink } from '@tabler/icons-react';
import useSWR from 'swr';
import FolderCard from '../FolderCard';
@@ -12,9 +12,20 @@ export default function FolderGridView() {
return (
<>
{isLoading ? (
<Paper withBorder h={200}>
<LoadingOverlay visible />
</Paper>
<SimpleGrid
my='sm'
spacing='md'
cols={{
base: 1,
md: 2,
lg: 4,
}}
pos='relative'
>
{[...Array(4)].map((_, i) => (
<Skeleton key={i} height={120} animate />
))}
</SimpleGrid>
) : (folders?.length ?? 0 !== 0) ? (
<SimpleGrid
my='sm'

View File

@@ -1,6 +1,6 @@
import { Response } from '@/lib/api/response';
import { Invite } from '@/lib/db/models/invite';
import { Center, Group, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Center, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconLink } from '@tabler/icons-react';
import useSWR from 'swr';
import InviteCard from '../InviteCard';
@@ -12,9 +12,20 @@ export default function InviteGridView() {
return (
<>
{isLoading ? (
<Paper withBorder h={200}>
<LoadingOverlay visible />
</Paper>
<SimpleGrid
my='sm'
spacing='md'
cols={{
base: 1,
md: 2,
lg: 4,
}}
pos='relative'
>
{[...Array(4)].map((_, i) => (
<Skeleton key={i} height={120} animate />
))}
</SimpleGrid>
) : (folders?.length ?? 0 !== 0) ? (
<SimpleGrid
my='sm'

View File

@@ -1,10 +1,12 @@
import { Box, Button, Group, Loader, Modal, Paper, SimpleGrid, Text, Title, Tooltip } from '@mantine/core';
import { Box, Button, Group, Modal, Paper, SimpleGrid, Text, Title, Tooltip } from '@mantine/core';
import { DatePicker } from '@mantine/dates';
import { IconCalendarSearch, IconCalendarTime } from '@tabler/icons-react';
import dynamic from 'next/dynamic';
import { useState } from 'react';
import FilesUrlsCountGraph from './parts/FilesUrlsCountGraph';
import { useApiStats } from './useStats';
import dynamic from 'next/dynamic';
import { StatsCardsSkeleton } from './parts/StatsCards';
import { StatsTablesSkeleton } from './parts/StatsTables';
const StatsCards = dynamic(() => import('./parts/StatsCards'));
const StatsTables = dynamic(() => import('./parts/StatsTables'));
@@ -92,7 +94,11 @@ export default function DashboardMetrics() {
<Box pos='relative' mih={300} my='sm'>
{isLoading ? (
<Loader />
<div>
<StatsCardsSkeleton />
<StatsTablesSkeleton />
</div>
) : data?.length ? (
<div>
<StatsCards data={data!} />

View File

@@ -1,6 +1,6 @@
import { bytes } from '@/lib/bytes';
import { Metric } from '@/lib/db/models/metric';
import { Group, Paper, SimpleGrid, Text, Title } from '@mantine/core';
import { Group, Paper, SimpleGrid, Skeleton, Text, Title } from '@mantine/core';
import {
IconDatabase,
IconEyeFilled,
@@ -26,6 +26,23 @@ function StatCard({ title, value, Icon }: { title: string; value: number | strin
);
}
export function StatsCardsSkeleton() {
return (
<SimpleGrid
cols={{
base: 1,
md: 2,
lg: 3,
}}
mb='sm'
>
{[...Array(6)].map((_, i) => (
<Skeleton key={i} height={100} animate />
))}
</SimpleGrid>
);
}
export default function StatsCards({ data }: { data: Metric[] }) {
if (!data.length) return null;
const recent = data[0];

View File

@@ -1,8 +1,117 @@
import { bytes } from '@/lib/bytes';
import { Metric } from '@/lib/db/models/metric';
import { Paper, ScrollArea, SimpleGrid, Table } from '@mantine/core';
import { Paper, ScrollArea, SimpleGrid, Skeleton, Table, Text } from '@mantine/core';
import TypesPieChart from './TypesPieChart';
function SkeletonText() {
return (
<Table.Td>
<Skeleton animate>
<Text>...</Text>
</Skeleton>
</Table.Td>
);
}
export function StatsTablesSkeleton() {
return (
<>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<Paper radius='sm' withBorder>
<ScrollArea.Autosize mah={500} type='auto'>
<Table highlightOnHover stickyHeader>
<Table.Thead>
<Table.Tr>
<Table.Th>User</Table.Th>
<Table.Th>Files</Table.Th>
<Table.Th>Storage Used</Table.Th>
<Table.Th>Views</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{[...Array(5)].map((i) => (
<Table.Tr key={i}>
<Table.Td>
<SkeletonText />
</Table.Td>
<Table.Td>
<SkeletonText />
</Table.Td>
<Table.Td>
<SkeletonText />
</Table.Td>
<Table.Td>
<SkeletonText />
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea.Autosize>
</Paper>
<Paper radius='sm' withBorder mah={500}>
<ScrollArea.Autosize mah={500} type='auto'>
<Table highlightOnHover stickyHeader>
<Table.Thead>
<Table.Tr>
<Table.Th>User</Table.Th>
<Table.Th>URLs</Table.Th>
<Table.Th>Views</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{[...Array(5)].map((i) => (
<Table.Tr key={i}>
<Table.Td>
<SkeletonText />
</Table.Td>
<Table.Td>
<SkeletonText />
</Table.Td>
<Table.Td>
<SkeletonText />
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea.Autosize>
</Paper>
<Paper radius='sm' withBorder>
<ScrollArea.Autosize mah={500} type='auto'>
<Table highlightOnHover stickyHeader>
<Table.Thead>
<Table.Tr>
<Table.Th>Type</Table.Th>
<Table.Th>Files</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{[...Array(5)].map((i) => (
<Table.Tr key={i}>
<Table.Td>
<SkeletonText />
</Table.Td>
<Table.Td>
<SkeletonText />
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea.Autosize>
</Paper>
<Paper radius='sm' withBorder p='sm'>
<Skeleton height={500} />
</Paper>
</SimpleGrid>
</>
);
}
export default function StatsTables({ data }: { data: Metric[] }) {
if (!data.length) return null;

View File

@@ -14,26 +14,6 @@ export default function TypesPieChart({ metric }: { metric: Metric }) {
type: 'outer',
content: '{name} - {percentage}',
}}
// legend={{
// position: 'bottom',
// pageNavigator: {
// marker: {
// style: {
// inactiveFill: theme.colorScheme === 'light' ? '#000' : '#fff',
// fill: theme.colorScheme === 'light' ? '#000' : '#fff',
// opacity: 0.8,
// size: 14,
// },
// },
// text: {
// style: {
// fill: theme.colorScheme === 'light' ? '#000' : '#fff',
// fontSize: 14,
// },
// },
// },
// maxWidth: isSmall ? 100 : 100,
// }}
legend={false}
interactions={[{ type: 'pie-legend-active' }, { type: 'element-active' }]}
/>

View File

@@ -1,9 +1,10 @@
import { Response } from '@/lib/api/response';
import { fetchApi } from '@/lib/fetchApi';
import { Button, Paper, Text, Title } from '@mantine/core';
import { Button, Paper, SimpleGrid, Skeleton, Text, Title } from '@mantine/core';
import { modals } from '@mantine/modals';
import { showNotification } from '@mantine/notifications';
import { IconLogout } from '@tabler/icons-react';
import Link from 'next/link';
import useSWR from 'swr';
export default function SettingsSessions() {
@@ -39,20 +40,31 @@ export default function SettingsSessions() {
<Paper withBorder p='sm'>
<Title order={2}>Sessions</Title>
<Text c='dimmed' mt='sm'>
You are currently logged into {isLoading ? '...' : (data?.other?.length ?? '...')} other devices
</Text>
<Skeleton visible={isLoading} animate mt='sm'>
<Text c='dimmed'>
You are currently logged into {isLoading ? '...' : (data?.other?.length ?? '...')} other devices
</Text>
</Skeleton>
<Button
fullWidth
color='red'
mt='md'
disabled={isLoading || !data?.other?.length}
onClick={handleLogOutOfAllDevices}
leftSection={<IconLogout size='1rem' />}
<SimpleGrid
cols={{
xs: 1,
sm: 2,
}}
mt='sm'
>
Log out everywhere
</Button>
<Button
color='red'
disabled={isLoading || !data?.other?.length}
onClick={handleLogOutOfAllDevices}
leftSection={<IconLogout size='1rem' />}
>
Log out everywhere
</Button>
<Button color='yellow' component={Link} href='/auth/logout' leftSection={<IconLogout size='1rem' />}>
Log out of this browser
</Button>
</SimpleGrid>
</Paper>
);
}

View File

@@ -1,6 +1,6 @@
import { Response } from '@/lib/api/response';
import type { Url } from '@/lib/db/models/url';
import { Center, Group, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Center, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconLink } from '@tabler/icons-react';
import { useState } from 'react';
import useSWR from 'swr';
@@ -16,9 +16,20 @@ export default function UrlGridView() {
<EditUrlModal url={selectedUrl} onClose={() => setSelectedUrl(null)} open={!!selectedUrl} />
{isLoading ? (
<Paper withBorder h={200}>
<LoadingOverlay visible />
</Paper>
<SimpleGrid
my='sm'
spacing='md'
cols={{
base: 1,
md: 2,
lg: 4,
}}
pos='relative'
>
{[...Array(4)].map((_, i) => (
<Skeleton key={i} height={120} animate />
))}
</SimpleGrid>
) : (urls?.length ?? 0 !== 0) ? (
<SimpleGrid
my='sm'

View File

@@ -1,6 +1,6 @@
import { Response } from '@/lib/api/response';
import { User } from '@/lib/db/models/user';
import { Center, Group, LoadingOverlay, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import { Center, Group, Paper, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
import { IconFilesOff } from '@tabler/icons-react';
import useSWR from 'swr';
import UserCard from '../UserCard';
@@ -12,9 +12,20 @@ export default function UserGridView() {
return (
<>
{isLoading ? (
<Paper withBorder h={200}>
<LoadingOverlay visible />
</Paper>
<SimpleGrid
my='sm'
spacing='md'
cols={{
base: 1,
md: 2,
lg: 4,
}}
pos='relative'
>
{[...Array(4)].map((_, i) => (
<Skeleton key={i} height={120} animate />
))}
</SimpleGrid>
) : (users?.length ?? 0 !== 0) ? (
<SimpleGrid
my='sm'

View File

@@ -50,8 +50,6 @@ export async function readThemes(): Promise<ZiplineTheme[]> {
handleOverrideColors(cat_mocha as unknown as ZiplineTheme),
);
console.log(parsedThemes);
return parsedThemes;
}