mirror of
https://github.com/diced/zipline.git
synced 2026-01-14 22:03:48 -08:00
feat: skeletons for most ui components
This commit is contained in:
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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' }}>
|
||||
|
||||
@@ -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} />)
|
||||
) : (
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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!} />
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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' }]}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -50,8 +50,6 @@ export async function readThemes(): Promise<ZiplineTheme[]> {
|
||||
handleOverrideColors(cat_mocha as unknown as ZiplineTheme),
|
||||
);
|
||||
|
||||
console.log(parsedThemes);
|
||||
|
||||
return parsedThemes;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user