add favicon to shortener card + update shortener card ui

main
TZGyn 1 year ago
parent 3592673cef
commit 57cebc7330
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

@ -4,8 +4,10 @@
import * as Tooltip from '$lib/components/ui/tooltip'
import * as Dialog from '$lib/components/ui/dialog'
import * as DropdownMenu from '$lib/components/ui/dropdown-menu'
import * as Avatar from '$lib/components/ui/avatar'
import { Badge } from '$lib/components/ui/badge'
import { ScrollArea } from '$lib/components/ui/scroll-area'
import { Separator } from '$lib/components/ui/separator'
import type { Shortener } from '$lib/db/types'
import {
BarChart,
@ -43,25 +45,10 @@
return ''
}
const showEditModal = async (e: MouseEvent) => {
if (innerWidth < 640) return
const { href } = e.currentTarget as HTMLAnchorElement
const result = await preloadData(href)
if (result.type === 'loaded' && result.status === 200) {
pushState(href, { editLink: result.data })
} else {
// something bad happened! try navigating
goto(href)
}
}
let editProjectLinkOpen = false
let editData: typeof $page.state.editLink
const showEditModalNew = async (code: string) => {
const showEditModal = async (code: string) => {
const href = `/links/${code}/edit`
const result = await preloadData(href)
@ -91,45 +78,27 @@
goto(href)
}
}
const hostDomain = 'https://' + shortener.link.split('/')[2]
</script>
<Card.Root class="w-full max-w-[500px]">
<Card.Root>
<Card.Header>
<Card.Title class="flex items-center justify-between gap-2">
<div class="flex items-center gap-2">
<a
href={'https://' + shortener_url + '/' + shortener.code}
target="_blank"
class="hover:underline">
{shortener_url + '/' + shortener.code}
</a>
<ExternalLink size={16} />
</div>
<div class="flex gap-4">
{#if shortener.projectName}
<Badge variant="secondary">{shortener.projectName}</Badge>
{/if}
<Badge variant="outline" class="flex gap-2">
{#if shortener.active}
<span
class="relative inline-flex h-2 w-2 rounded-full bg-green-400"
></span>
Active
{:else}
<span
class="relative inline-flex h-2 w-2 rounded-full bg-gray-600"
></span>
Inactive
{/if}
</Badge>
</div>
</Card.Title>
<Card.Description>
<div class="flex items-center gap-2">
<Card.Title class="flex gap-4 justify-between items-center">
<Avatar.Root>
<Avatar.Image
src={hostDomain + '/favicon.ico'}
alt="favicon" />
<Avatar.Fallback class="bg-opacity-0">
<img src="/favicon.png" alt="favicon" />
</Avatar.Fallback>
</Avatar.Root>
<div class="flex flex-col flex-grow gap-2 items-start">
<Tooltip.Root>
<Tooltip.Trigger>
<div
class="max-w-[200px] overflow-hidden overflow-ellipsis whitespace-nowrap">
class="whitespace-nowrap max-w-[250px] overflow-x-clip overflow-ellipsis">
{shortener.link}
</div>
</Tooltip.Trigger>
@ -137,6 +106,65 @@
<p>{shortener.link}</p>
</Tooltip.Content>
</Tooltip.Root>
<div
class="flex gap-2 items-center text-sm text-muted-foreground">
<a
href={'https://' + shortener_url + '/' + shortener.code}
target="_blank"
class="hover:underline">
{shortener_url + '/' + shortener.code}
</a>
<ExternalLink size={16} />
</div>
</div>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<MoreVertical />
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<a
href={`/links/${shortener.code}/edit`}
on:click|preventDefault={() =>
showEditModal(shortener.code)}>
<DropdownMenu.Item class="flex gap-2 items-center">
<EditIcon size={16} />Edit
</DropdownMenu.Item>
</a>
<DropdownMenu.Item
on:click={() => openDeleteDialog(shortener.code)}
class="flex gap-2 items-center text-destructive data-[highlighted]:bg-destructive">
<TrashIcon size={16} />
Delete
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Card.Title>
</Card.Header>
<Card.Content>
<div class="flex justify-between items-center">
<div class="flex gap-2">
<Button
href={`/links/${shortener.code}`}
class="flex gap-1 justify-center items-center h-8 text-sm rounded bg-secondary">
<BarChart size={20} />
<div>
{shortener.visitorCount} visits
</div>
</Button>
<a
class={cn(
buttonVariants({ variant: 'default' }),
'flex h-8 items-center justify-center gap-1 rounded bg-secondary text-sm',
)}
href={`${getUrl()}/links/${shortener.code}/qr`}
on:click|preventDefault={showQRModal}>
<QrCode size={20} />
</a>
<Separator orientation="vertical" />
{#if shortener.ios}
<Tooltip.Root>
<Tooltip.Trigger>
@ -159,52 +187,24 @@
</Tooltip.Root>
{/if}
</div>
</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex items-center justify-between">
<div class="flex gap-2">
<Button
href={`/links/${shortener.code}`}
class="bg-secondary flex h-8 items-center justify-center gap-1 rounded text-sm">
<BarChart size={20} />
<div>
{shortener.visitorCount} visits
</div>
</Button>
<a
class={cn(
buttonVariants({ variant: 'default' }),
'bg-secondary flex h-8 items-center justify-center gap-1 rounded text-sm',
)}
href={`${getUrl()}/links/${shortener.code}/qr`}
on:click|preventDefault={showQRModal}>
<QrCode size={20} />
</a>
<div class="flex gap-4">
{#if shortener.projectName}
<Badge variant="secondary">{shortener.projectName}</Badge>
{/if}
<Badge variant="outline" class="flex gap-2">
{#if shortener.active}
<span
class="inline-flex relative w-2 h-2 bg-green-400 rounded-full"
></span>
Active
{:else}
<span
class="inline-flex relative w-2 h-2 bg-gray-600 rounded-full"
></span>
Inactive
{/if}
</Badge>
</div>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<MoreVertical />
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Group>
<a
href={`/links/${shortener.code}/edit`}
on:click|preventDefault={() =>
showEditModalNew(shortener.code)}>
<DropdownMenu.Item class="flex items-center gap-2">
<EditIcon size={16} />Edit
</DropdownMenu.Item>
</a>
<DropdownMenu.Item
on:click={() => openDeleteDialog(shortener.code)}
class="text-destructive data-[highlighted]:bg-destructive flex items-center gap-2">
<TrashIcon size={16} />
Delete
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
</Card.Content>
</Card.Root>

@ -80,7 +80,7 @@
</script>
<div
class="flex flex-wrap-reverse items-center justify-start gap-4 p-4">
class="flex flex-wrap-reverse gap-4 justify-start items-center p-4">
<Drawer.Root>
<Drawer.Trigger class="md:hidden">
<Button size="icon"><SortAscIcon /></Button>
@ -124,10 +124,10 @@
class="justify-between">
{selectedProject}
<ChevronsUpDown
class="ml-2 h-4 w-4 shrink-0 opacity-50" />
class="ml-2 w-4 h-4 opacity-50 shrink-0" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Popover.Content class="p-0 w-[200px]">
<Command.Root>
<Command.Input placeholder="Search project..." />
<Command.Empty>No project found.</Command.Empty>
@ -186,7 +186,7 @@
</Command.Root>
</Popover.Content>
</Popover.Root>
<div class="flex items-center gap-4">
<div class="flex gap-4 items-center">
<Input
type="text"
placeholder="search"
@ -208,7 +208,7 @@
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
<div class="hidden items-center gap-4 md:flex">
<div class="hidden gap-4 items-center md:flex">
<Popover.Root bind:open>
<Popover.Trigger asChild let:builder>
<Button
@ -216,12 +216,12 @@
variant="outline"
role="combobox"
aria-expanded={open}
class="w-[200px] justify-between">
class="justify-between w-[200px]">
{selectedProject}
<ChevronsUpDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
<ChevronsUpDown class="ml-2 w-4 h-4 opacity-50 shrink-0" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-[200px] p-0">
<Popover.Content class="p-0 w-[200px]">
<Command.Root>
<Command.Input placeholder="Search project..." />
<Command.Empty>No project found.</Command.Empty>
@ -304,7 +304,7 @@
<Select.Input name="favoriteFruit" />
</Select.Root>
</div>
<div class="hidden items-center gap-4 sm:flex">
<div class="hidden gap-4 items-center sm:flex">
<Input
type="text"
placeholder="search"
@ -326,13 +326,14 @@
{#await data.shorteners}
<div class="flex flex-wrap gap-4 p-4">
{#each [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as _}
<Skeleton class="h-[150px] w-[500px] rounded-lg" />
<Skeleton class="rounded-lg h-[150px] w-[500px]" />
{/each}
</div>
{:then shorteners}
{#if shorteners.length > 0}
<ScrollArea class="flex-grow">
<div class="flex flex-wrap gap-4 p-4">
<div
class="grid grid-cols-[repeat(auto-fit,_minmax(500px,_1fr))] gap-4 p-4">
{#each shorteners as shortener}
<ShortenerCard
{shortener}
@ -345,11 +346,11 @@
{:else}
<div class="flex flex-grow p-4">
<div
class="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm">
class="flex flex-1 justify-center items-center rounded-lg border border-dashed shadow-sm">
<div
class="flex w-full flex-grow items-center justify-center">
<div class="flex flex-col items-center gap-8">
<div class="flex flex-col items-center gap-2">
class="flex flex-grow justify-center items-center w-full">
<div class="flex flex-col gap-8 items-center">
<div class="flex flex-col gap-2 items-center">
<div class="text-4xl font-bold">No Shortener Found</div>
<p class="text-muted-foreground">Add a new shortener</p>
</div>
@ -366,7 +367,7 @@
{/await}
{#await data.pagination then pagination}
<div class="flex items-center justify-between border-t p-4">
<div class="flex justify-between items-center p-4 border-t">
<Select.Root
selected={{ label: data.perPage, value: data.perPage }}>
<Select.Trigger class="w-[180px]">

@ -4,8 +4,10 @@
import * as Dialog from '$lib/components/ui/dialog'
import * as DropdownMenu from '$lib/components/ui/dropdown-menu'
import * as Tooltip from '$lib/components/ui/tooltip'
import * as Avatar from '$lib/components/ui/avatar'
import { Badge } from '$lib/components/ui/badge'
import { ScrollArea } from '$lib/components/ui/scroll-area'
import { Separator } from '$lib/components/ui/separator'
import type { Shortener, Project, Setting } from '$lib/db/types'
import {
BarChart,
@ -35,36 +37,10 @@
deleteDialogOpen = true
}
const showEditModal = async (e: MouseEvent) => {
// bail if opening a new tab, or we're on too small a screen
if (innerWidth < 640) return
// // prevent navigation
// e.preventDefault()
//
const { href } = e.currentTarget as HTMLAnchorElement
// run `load` functions (or rather, get the result of the `load` functions
// that are already running because of `data-sveltekit-preload-data`)
// const href = `/projects/${selected_project.uuid}/links/${shortener.code}/edit`
const result = await preloadData(href)
if (result.type === 'loaded' && result.status === 200) {
pushState(href, { editProjectLink: result.data })
} else {
// something bad happened! try navigating
goto(href)
}
}
let editProjectLinkOpen = false
let editData: typeof $page.state.editProjectLink
const showEditModalNew = async (
projectUuid: string,
code: string,
) => {
const showEditModal = async (projectUuid: string, code: string) => {
const href = `/projects/${projectUuid}/links/${code}/edit`
const result = await preloadData(href)
@ -90,42 +66,26 @@
goto(href)
}
}
const hostDomain = 'https://' + shortener.link.split('/')[2]
</script>
<Card.Root class="w-full max-w-[500px]">
<Card.Root>
<Card.Header>
<Card.Title class="flex items-center justify-between gap-2">
<div class="flex items-center gap-2">
<a
href={'https://' + shortener_url + '/' + shortener.code}
target="_blank"
class="hover:underline">
{shortener_url + '/' + shortener.code}
</a>
<ExternalLink size={16} />
</div>
<div class="flex gap-4">
<Badge variant="outline" class="flex gap-2">
{#if shortener.active}
<span
class="relative inline-flex h-2 w-2 rounded-full bg-green-400"
></span>
Active
{:else}
<span
class="relative inline-flex h-2 w-2 rounded-full bg-gray-600"
></span>
Inactive
{/if}
</Badge>
</div>
</Card.Title>
<Card.Description>
<div class="flex items-center gap-2">
<Card.Title class="flex gap-4 justify-between items-center">
<Avatar.Root>
<Avatar.Image
src={hostDomain + '/favicon.ico'}
alt="favicon" />
<Avatar.Fallback class="bg-opacity-0">
<img src="/favicon.png" alt="favicon" />
</Avatar.Fallback>
</Avatar.Root>
<div class="flex flex-col flex-grow gap-2 items-start">
<Tooltip.Root>
<Tooltip.Trigger>
<div
class="max-w-[200px] overflow-hidden overflow-ellipsis whitespace-nowrap">
class="whitespace-nowrap max-w-[250px] overflow-x-clip overflow-ellipsis">
{shortener.link}
</div>
</Tooltip.Trigger>
@ -133,51 +93,19 @@
<p>{shortener.link}</p>
</Tooltip.Content>
</Tooltip.Root>
{#if shortener.ios}
<Tooltip.Root>
<Tooltip.Trigger>
<Badge variant="outline" class="flex gap-2">iOS</Badge>
</Tooltip.Trigger>
<Tooltip.Content>
<p>{shortener.ios_link}</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
{#if shortener.android}
<Tooltip.Root>
<Tooltip.Trigger>
<Badge variant="outline" class="flex gap-2"
>Android</Badge>
</Tooltip.Trigger>
<Tooltip.Content>
<p>{shortener.android_link}</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
</div>
</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex items-center justify-between">
<div class="flex gap-2">
<Button
href={`/links/${shortener.code}`}
class="bg-secondary flex h-8 items-center justify-center gap-1 rounded text-sm">
<BarChart size={20} />
<div>
{shortener.visitorCount} visits
</div>
</Button>
<a
class={cn(
buttonVariants({ variant: 'default' }),
'bg-secondary flex h-8 items-center justify-center gap-1 rounded text-sm',
)}
href={`/projects/${selected_project.uuid}/links/${shortener.code}/qr`}
on:click|preventDefault={showQRModal}>
<QrCode size={20} />
</a>
<div
class="flex gap-2 items-center text-sm text-muted-foreground">
<a
href={'https://' + shortener_url + '/' + shortener.code}
target="_blank"
class="hover:underline">
{shortener_url + '/' + shortener.code}
</a>
<ExternalLink size={16} />
</div>
</div>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<MoreVertical />
@ -194,23 +122,85 @@
<a
href={`/projects/${selected_project.uuid}/links/${shortener.code}/edit`}
on:click|preventDefault={() =>
showEditModalNew(
showEditModal(
selected_project.uuid || '',
shortener.code,
)}>
<DropdownMenu.Item class="flex items-center gap-2">
<DropdownMenu.Item class="flex gap-2 items-center">
<EditIcon size={16} />Edit
</DropdownMenu.Item>
</a>
<DropdownMenu.Item
on:click={() => openDeleteDialog(shortener.code)}
class="text-destructive data-[highlighted]:bg-destructive flex items-center gap-2">
class="flex gap-2 items-center text-destructive data-[highlighted]:bg-destructive">
<TrashIcon size={16} />
Delete
</DropdownMenu.Item>
</DropdownMenu.Group>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Card.Title>
</Card.Header>
<Card.Content>
<div class="flex justify-between items-center">
<div class="flex gap-2">
<Button
href={`/links/${shortener.code}`}
class="flex gap-1 justify-center items-center h-8 text-sm rounded bg-secondary">
<BarChart size={20} />
<div>
{shortener.visitorCount} visits
</div>
</Button>
<a
class={cn(
buttonVariants({ variant: 'default' }),
'flex h-8 items-center justify-center gap-1 rounded bg-secondary text-sm',
)}
href={`/projects/${selected_project.uuid}/links/${shortener.code}/qr`}
on:click|preventDefault={showQRModal}>
<QrCode size={20} />
</a>
{#if shortener.ios}
<Tooltip.Root>
<Tooltip.Trigger>
<Badge variant="outline" class="flex gap-2">iOS</Badge>
</Tooltip.Trigger>
<Tooltip.Content>
<p>{shortener.ios_link}</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
{#if shortener.android}
<Tooltip.Root>
<Tooltip.Trigger>
<Badge variant="outline" class="flex gap-2"
>Android</Badge>
</Tooltip.Trigger>
<Tooltip.Content>
<p>{shortener.android_link}</p>
</Tooltip.Content>
</Tooltip.Root>
{/if}
</div>
<div class="flex gap-4">
{#if shortener.projectName}
<Badge variant="secondary">{shortener.projectName}</Badge>
{/if}
<Badge variant="outline" class="flex gap-2">
{#if shortener.active}
<span
class="inline-flex relative w-2 h-2 bg-green-400 rounded-full"
></span>
Active
{:else}
<span
class="inline-flex relative w-2 h-2 bg-gray-600 rounded-full"
></span>
Inactive
{/if}
</Badge>
</div>
</div>
</Card.Content>
</Card.Root>

@ -64,7 +64,7 @@
</script>
<div
class="flex flex-wrap-reverse items-center justify-start gap-4 px-4 py-4 md:px-10">
class="flex flex-wrap-reverse gap-4 justify-start items-center py-4 px-4 md:px-10">
<Drawer.Root>
<Drawer.Trigger class="md:hidden">
<Button size="icon"><SortAscIcon /></Button>
@ -120,7 +120,7 @@
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
<div class="hidden items-center gap-4 md:flex">
<div class="hidden gap-4 items-center md:flex">
<Select.Root
selected={{ label: data.sortBy, value: data.sortBy }}>
<Select.Trigger class="w-[180px]" customIcon={SortDescIcon}>
@ -145,7 +145,7 @@
<Select.Input name="favoriteFruit" />
</Select.Root>
</div>
<div class="hidden items-center gap-4 sm:flex">
<div class="hidden gap-4 items-center sm:flex">
<Input
type="text"
placeholder="search"
@ -165,15 +165,16 @@
</div>
{#await data.shorteners}
<div class="flex flex-wrap gap-4 px-4 py-4 md:px-10">
<div class="flex flex-wrap gap-4 py-4 px-4 md:px-10">
{#each [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as _}
<Skeleton class="h-[150px] w-[500px] rounded-lg" />
<Skeleton class="rounded-lg h-[150px] w-[500px]" />
{/each}
</div>
{:then shorteners}
{#if shorteners.length > 0}
<ScrollArea class="flex-grow">
<div class="flex flex-wrap gap-4 px-4 py-4 md:px-10">
<div
class="grid grid-cols-[repeat(auto-fit,_minmax(500px,_1fr))] gap-4 px-4 py-4 md:px-10">
{#each shorteners as shortener}
<ShortenerCard
{shortener}
@ -184,13 +185,13 @@
</div>
</ScrollArea>
{:else}
<div class="flex flex-grow px-4 py-4 md:px-10">
<div class="flex flex-grow py-4 px-4 md:px-10">
<div
class="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm">
class="flex flex-1 justify-center items-center rounded-lg border border-dashed shadow-sm">
<div
class="flex w-full flex-grow items-center justify-center">
<div class="flex flex-col items-center gap-8">
<div class="flex flex-col items-center gap-2">
class="flex flex-grow justify-center items-center w-full">
<div class="flex flex-col gap-8 items-center">
<div class="flex flex-col gap-2 items-center">
<div class="text-4xl font-bold">No Shortener Found</div>
<p class="text-muted-foreground">Add a new shortener</p>
</div>
@ -207,7 +208,7 @@
{/await}
{#await data.pagination then pagination}
<div class="flex items-center justify-between border-t p-4">
<div class="flex justify-between items-center p-4 border-t">
<Select.Root
selected={{ label: data.perPage, value: data.perPage }}>
<Select.Trigger class="w-[180px]">

Loading…
Cancel
Save