mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 15:05:35 +00:00
feat: add multiple way to show collections and links
This commit is contained in:
66
inertia/components/common/combo_list/combo_list.tsx
Normal file
66
inertia/components/common/combo_list/combo_list.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Combobox, Input, InputBase, useCombobox } from '@mantine/core';
|
||||
import { ComboListItem } from '~/components/common/combo_list/combo_list_item';
|
||||
|
||||
export type ValueWithIcon = {
|
||||
label: string;
|
||||
value: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
export function ComboList({
|
||||
selectedValue,
|
||||
values,
|
||||
setValue,
|
||||
}: {
|
||||
selectedValue: string;
|
||||
values: ValueWithIcon[];
|
||||
setValue: (value: string) => void;
|
||||
}) {
|
||||
const combobox = useCombobox({
|
||||
onDropdownClose: () => combobox.resetSelectedOption(),
|
||||
});
|
||||
|
||||
const selectedOption = values.find((item) => item.value === selectedValue);
|
||||
|
||||
const options = values.map((item) => (
|
||||
<Combobox.Option value={item.value} key={item.value}>
|
||||
<ComboListItem emoji={item.icon} label={item.label} />
|
||||
</Combobox.Option>
|
||||
));
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
store={combobox}
|
||||
withinPortal={false}
|
||||
onOptionSubmit={(val) => {
|
||||
setValue(val as string);
|
||||
combobox.closeDropdown();
|
||||
}}
|
||||
>
|
||||
<Combobox.Target>
|
||||
<InputBase
|
||||
component="button"
|
||||
type="button"
|
||||
pointer
|
||||
rightSection={<Combobox.Chevron />}
|
||||
onClick={() => combobox.toggleDropdown()}
|
||||
rightSectionPointerEvents="none"
|
||||
multiline
|
||||
>
|
||||
{selectedOption ? (
|
||||
<ComboListItem
|
||||
emoji={selectedOption.icon}
|
||||
label={selectedOption.label}
|
||||
/>
|
||||
) : (
|
||||
<Input.Placeholder>Pick value</Input.Placeholder>
|
||||
)}
|
||||
</InputBase>
|
||||
</Combobox.Target>
|
||||
|
||||
<Combobox.Dropdown>
|
||||
<Combobox.Options>{options}</Combobox.Options>
|
||||
</Combobox.Dropdown>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
16
inertia/components/common/combo_list/combo_list_item.tsx
Normal file
16
inertia/components/common/combo_list/combo_list_item.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Group, Text } from '@mantine/core';
|
||||
|
||||
export const ComboListItem = ({
|
||||
emoji,
|
||||
label,
|
||||
}: {
|
||||
emoji: React.ReactNode;
|
||||
label: string;
|
||||
}) => (
|
||||
<Group gap="xs" align="center">
|
||||
{emoji}
|
||||
<Text fz="sm" fw={500}>
|
||||
{label}
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
@@ -19,6 +19,8 @@ import { useEffect } from 'react';
|
||||
import { UserDropdown } from '~/components/common/floating_navbar/user_dropdown';
|
||||
import { ExternalLinkUnstyled } from '~/components/common/links/external_link_unstyled';
|
||||
import { InternalLink } from '~/components/common/links/internal_link';
|
||||
import { LocaleSwitcher } from '~/components/common/locale_switcher';
|
||||
import { ThemeSwitcher } from '~/components/common/theme_switcher';
|
||||
import { useAuth } from '~/hooks/use_auth';
|
||||
import classes from './floating_navbar.module.css';
|
||||
|
||||
@@ -74,8 +76,8 @@ export function FloatingNavbar({ width }: FloatingNavbarProps) {
|
||||
</Group>
|
||||
<Group>
|
||||
{!isMobile && <Group>{links}</Group>}
|
||||
{isMobile && <Burger opened={opened} onClick={handler.toggle} />}
|
||||
{auth.isAuthenticated && <UserDropdown />}
|
||||
{isMobile && <Burger opened={opened} onClick={handler.toggle} />}
|
||||
{!auth.isAuthenticated && (
|
||||
<Button
|
||||
variant="default"
|
||||
@@ -101,6 +103,10 @@ export function FloatingNavbar({ width }: FloatingNavbarProps) {
|
||||
<Flex direction="column" gap="md">
|
||||
{links}
|
||||
</Flex>
|
||||
<Group mt="md">
|
||||
<ThemeSwitcher />
|
||||
<LocaleSwitcher />
|
||||
</Group>
|
||||
</Drawer>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
var(--mantine-color-dark-8)
|
||||
);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.userActive {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Avatar, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { modals } from '@mantine/modals';
|
||||
import cx from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TbChevronDown, TbLogout, TbShield } from 'react-icons/tb';
|
||||
import { TbChevronDown, TbLogout, TbSettings, TbShield } from 'react-icons/tb';
|
||||
import { InternalLinkUnstyled } from '~/components/common/links/internal_link_unstyled';
|
||||
import { UserPreferences } from '~/components/common/user_preferences/user_preferences';
|
||||
import { useAuth } from '~/hooks/use_auth';
|
||||
import classes from './user_dropdown.module.css';
|
||||
|
||||
@@ -12,6 +14,14 @@ export function UserDropdown() {
|
||||
const [userMenuOpened, { open: openUserMenu, close: closeUserMenu }] =
|
||||
useDisclosure(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handlePreferencesModal = () => {
|
||||
modals.open({
|
||||
title: t('user-preferences'),
|
||||
children: <UserPreferences />,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu
|
||||
width={260}
|
||||
@@ -49,12 +59,18 @@ export function UserDropdown() {
|
||||
href="/admin"
|
||||
color="red"
|
||||
>
|
||||
{t('common:manage_users')}
|
||||
{t('common:manage-users')}
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Menu.Label>{t('common:settings')}</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<TbSettings size={16} />}
|
||||
onClick={handlePreferencesModal}
|
||||
>
|
||||
{t('common:preferences')}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
leftSection={<TbLogout size={16} />}
|
||||
component={InternalLinkUnstyled}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { LinkListDisplay } from '#shared/types/index';
|
||||
import { Fieldset, Stack, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ComboList } from '~/components/common/combo_list/combo_list';
|
||||
import { CollectionListSelector } from '~/components/dashboard/collection/collection_list_selector';
|
||||
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
||||
import { useIsMobile } from '~/hooks/use_is_mobile';
|
||||
import { getLinkListDisplayOptions } from '~/lib/display_preferences';
|
||||
|
||||
export function UserPreferences() {
|
||||
const { displayPreferences, handleUpdateDisplayPreferences } =
|
||||
useDisplayPreferences();
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Fieldset legend={t('preferences')}>
|
||||
{isMobile && (
|
||||
<Text size="xs" c="orange" mb="sm">
|
||||
{t('preferences-description')}
|
||||
</Text>
|
||||
)}
|
||||
<Stack>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('display-preferences.collection-list-display')}
|
||||
</Text>
|
||||
<CollectionListSelector />
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('display-preferences.link-list-display')}
|
||||
</Text>
|
||||
<ComboList
|
||||
selectedValue={displayPreferences.linkListDisplay}
|
||||
values={getLinkListDisplayOptions()}
|
||||
setValue={(value) =>
|
||||
handleUpdateDisplayPreferences({
|
||||
linkListDisplay: value as LinkListDisplay,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
29
inertia/components/dashboard/collection/collection_list.tsx
Normal file
29
inertia/components/dashboard/collection/collection_list.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ScrollArea, Stack, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CollectionFavoriteItem } from '~/components/dashboard/collection/item/collection_favorite_item';
|
||||
import { CollectionItem } from '~/components/dashboard/collection/item/collection_item';
|
||||
import { useCollections } from '~/hooks/collections/use_collections';
|
||||
import { useIsMobile } from '~/hooks/use_is_mobile';
|
||||
import styles from './list/collection_list.module.css';
|
||||
|
||||
export function CollectionList() {
|
||||
const { t } = useTranslation();
|
||||
const collections = useCollections();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Stack gap="xs" h="100%" w={isMobile ? '100%' : '350px'}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Text c="dimmed" ml="md" mb="sm">
|
||||
{t('collection.collections')} • {collections.length}
|
||||
</Text>
|
||||
<ScrollArea className={styles.collectionList}>
|
||||
<CollectionFavoriteItem />
|
||||
{collections.map((collection) => (
|
||||
<CollectionItem collection={collection} />
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CollectionListDisplay } from '#shared/types/index';
|
||||
import { ComboList } from '~/components/common/combo_list/combo_list';
|
||||
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
||||
import { getCollectionListDisplayOptions } from '~/lib/display_preferences';
|
||||
|
||||
export function CollectionListSelector() {
|
||||
const { displayPreferences, handleUpdateDisplayPreferences } =
|
||||
useDisplayPreferences();
|
||||
return (
|
||||
<ComboList
|
||||
selectedValue={displayPreferences.collectionListDisplay}
|
||||
values={getCollectionListDisplayOptions()}
|
||||
setValue={(value) =>
|
||||
handleUpdateDisplayPreferences({
|
||||
collectionListDisplay: value as CollectionListDisplay,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { router } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { Chip, Group, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { useCollections } from '~/hooks/collections/use_collections';
|
||||
import { appendCollectionId } from '~/lib/navigation';
|
||||
|
||||
export function InlineCollectionList() {
|
||||
const { t } = useTranslation();
|
||||
const collections = useCollections();
|
||||
const activeCollection = useActiveCollection();
|
||||
|
||||
const handleCollectionChange = (value?: string) => {
|
||||
if (value) {
|
||||
router.visit(appendCollectionId(route('dashboard').path, Number(value)));
|
||||
return;
|
||||
}
|
||||
router.visit(route('dashboard').path);
|
||||
};
|
||||
|
||||
const fields = [
|
||||
{
|
||||
label: t('common:favorite'),
|
||||
value: 'favorite',
|
||||
},
|
||||
...collections.map((c) => ({
|
||||
label: (
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<>{c.name}</>
|
||||
<Text size="xs" c="dimmed">
|
||||
{c.links.length}
|
||||
</Text>
|
||||
</Group>
|
||||
),
|
||||
value: c.id.toString(),
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<Group gap="xs" w="100%">
|
||||
{fields.map((field) => (
|
||||
<Chip
|
||||
key={field.value}
|
||||
checked={
|
||||
activeCollection?.id
|
||||
? activeCollection.id === Number(field.value)
|
||||
: field.value === 'favorite'
|
||||
}
|
||||
variant="light"
|
||||
onClick={() => handleCollectionChange(field.value)}
|
||||
>
|
||||
{field.label}
|
||||
</Chip>
|
||||
))}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TbStar, TbStarFilled } from 'react-icons/tb';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import classes from './collection_item.module.css';
|
||||
|
||||
export function CollectionFavoriteItem() {
|
||||
const { t } = useTranslation();
|
||||
const activeCollection = useActiveCollection();
|
||||
const isActiveCollection = !activeCollection?.id;
|
||||
const FolderIcon = isActiveCollection ? TbStarFilled : TbStar;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={classes.link}
|
||||
data-active={isActiveCollection || undefined}
|
||||
href={route('dashboard').path}
|
||||
key="favorite"
|
||||
title="Favorite"
|
||||
>
|
||||
<FolderIcon className={classes.linkIcon} />
|
||||
<Text maw={'200px'} style={{ wordBreak: 'break-all' }}>
|
||||
{t('favorite')}
|
||||
</Text>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import { appendCollectionId } from '~/lib/navigation';
|
||||
import { CollectionWithLinks } from '~/types/app';
|
||||
import classes from './collection_item.module.css';
|
||||
|
||||
export default function CollectionItem({
|
||||
collection,
|
||||
}: {
|
||||
interface CollectionItemProps {
|
||||
collection: CollectionWithLinks;
|
||||
}) {
|
||||
}
|
||||
|
||||
export function CollectionItem({ collection }: CollectionItemProps) {
|
||||
const itemRef = useRef<HTMLAnchorElement>(null);
|
||||
const activeCollection = useActiveCollection();
|
||||
const isActiveCollection = collection.id === activeCollection?.id;
|
||||
@@ -34,7 +34,10 @@ export default function CollectionItem({
|
||||
title={collection.name}
|
||||
>
|
||||
<FolderIcon className={classes.linkIcon} />
|
||||
<Text lineClamp={1} maw={'200px'} style={{ wordBreak: 'break-all' }}>
|
||||
<Text
|
||||
lineClamp={1}
|
||||
style={{ wordBreak: 'break-all', whiteSpace: 'pre-line' }}
|
||||
>
|
||||
{collection.name}
|
||||
</Text>
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Button, Drawer, Portal, rem, Text } from '@mantine/core';
|
||||
import { useDisclosure, useHeadroom } from '@mantine/hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TbFolder } from 'react-icons/tb';
|
||||
import { CollectionFavoriteItem } from '~/components/dashboard/collection/item/collection_favorite_item';
|
||||
import { useCollections } from '~/hooks/collections/use_collections';
|
||||
import { CollectionItem } from './item/collection_item';
|
||||
|
||||
export function MobileCollectionList() {
|
||||
const { t } = useTranslation();
|
||||
const [opened, handler] = useDisclosure();
|
||||
const collections = useCollections();
|
||||
const pinned = useHeadroom({ fixedAt: 0 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
opened={opened}
|
||||
onClose={handler.close}
|
||||
title={t('collection.collections', { count: collections.length })}
|
||||
>
|
||||
<CollectionFavoriteItem />
|
||||
{collections.map((collection) => (
|
||||
<CollectionItem collection={collection} />
|
||||
))}
|
||||
</Drawer>
|
||||
<Portal>
|
||||
<Button
|
||||
onClick={handler.open}
|
||||
variant="outline"
|
||||
size="xs"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: '50%',
|
||||
bottom: pinned ? rem(16) : rem(-100),
|
||||
width: `calc(100% - ${rem(16)} * 2)`,
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
<TbFolder size={18} />
|
||||
<Text ml={4}>
|
||||
{t('collection.collections', { count: collections.length })}
|
||||
</Text>
|
||||
</Button>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Badge, CopyButton } from '@mantine/core';
|
||||
import { t } from 'i18next';
|
||||
import { TbCopy } from 'react-icons/tb';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { useAppUrl } from '~/hooks/use_app_url';
|
||||
|
||||
const COPY_TIMEOUT = 3_000;
|
||||
|
||||
export function SharedCollectionCopyLink() {
|
||||
const appUrl = useAppUrl();
|
||||
const activeCollection = useActiveCollection();
|
||||
|
||||
if (!activeCollection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const copyUrl = `${appUrl}/shared/${activeCollection.id}`;
|
||||
return (
|
||||
<CopyButton value={copyUrl} timeout={COPY_TIMEOUT}>
|
||||
{({ copied, copy }) => (
|
||||
<Badge
|
||||
variant={copied ? 'filled' : 'light'}
|
||||
onClick={copy}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{copied ? t('success-copy') : t('visibility.public')}
|
||||
{!copied && <TbCopy style={{ marginLeft: 4 }} />}
|
||||
</Badge>
|
||||
)}
|
||||
</CopyButton>
|
||||
);
|
||||
}
|
||||
@@ -28,7 +28,13 @@ export function LinkItem({ link, hideMenu: hideMenu = false }: LinkItemProps) {
|
||||
{!hideMenu && <LinkControls link={link} />}
|
||||
</Group>
|
||||
{description && (
|
||||
<Text c="dimmed" size="sm" mt="xs" lineClamp={3}>
|
||||
<Text
|
||||
c="dimmed"
|
||||
size="sm"
|
||||
mt="xs"
|
||||
lineClamp={3}
|
||||
style={{ wordBreak: 'break-word', whiteSpace: 'pre-line' }}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -1,24 +1,52 @@
|
||||
import { Stack } from '@mantine/core';
|
||||
import { DisplayPreferences } from '#shared/types/index';
|
||||
import { SimpleGrid, Stack, StyleProp, useCombobox } from '@mantine/core';
|
||||
import { LinkItem } from '~/components/dashboard/link/item/link_item';
|
||||
import { NoLink } from '~/components/dashboard/link/no_link/no_link';
|
||||
import { useActiveCollection } from '~/stores/collection_store';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { useFavoriteLinks } from '~/hooks/collections/use_favorite_links';
|
||||
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
||||
|
||||
export interface LinkListProps {
|
||||
hideMenu?: boolean;
|
||||
}
|
||||
|
||||
export function LinkList({ hideMenu = false }: LinkListProps) {
|
||||
const { activeCollection } = useActiveCollection();
|
||||
const activeCollection = useActiveCollection();
|
||||
const favoriteLinks = useFavoriteLinks();
|
||||
const { displayPreferences } = useDisplayPreferences();
|
||||
|
||||
if (!activeCollection?.links || activeCollection.links.length === 0) {
|
||||
const combobox = useCombobox({
|
||||
onDropdownClose: () => combobox.resetSelectedOption(),
|
||||
});
|
||||
|
||||
const links = activeCollection?.links || favoriteLinks;
|
||||
|
||||
if (activeCollection?.links.length === 0) {
|
||||
return <NoLink hideMenu={hideMenu} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap="xs">
|
||||
{activeCollection?.links.map((link) => (
|
||||
<LinkItem link={link} key={link.id} hideMenu={hideMenu} />
|
||||
))}
|
||||
<SimpleGrid cols={getColsByView(displayPreferences)} spacing="xs">
|
||||
{links.map((link) => (
|
||||
<LinkItem link={link} key={link.id} hideMenu={hideMenu} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function getColsByView(
|
||||
displayPreferences: DisplayPreferences
|
||||
): StyleProp<number> {
|
||||
const { linkListDisplay } = displayPreferences;
|
||||
|
||||
if (linkListDisplay === 'grid') {
|
||||
return {
|
||||
sm: 1,
|
||||
md: 2,
|
||||
lg: 3,
|
||||
};
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Box, Group, SegmentedControl, Text, TextInput } from '@mantine/core';
|
||||
import { FormEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import BackToDashboard from '~/components/common/navigation/back_to_dashboard';
|
||||
import useSearchParam from '~/hooks/use_search_param';
|
||||
import { FormLayout, FormLayoutProps } from '~/layouts/form_layout';
|
||||
import { Visibility } from '~/types/app';
|
||||
|
||||
@@ -29,6 +30,7 @@ export default function MantineFormCollection({
|
||||
...props
|
||||
}: FormCollectionProps) {
|
||||
const { t } = useTranslation('common');
|
||||
const collectionId = Number(useSearchParam('collectionId'));
|
||||
|
||||
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -36,7 +38,7 @@ export default function MantineFormCollection({
|
||||
};
|
||||
|
||||
return (
|
||||
<FormLayout handleSubmit={onSubmit} {...props}>
|
||||
<FormLayout handleSubmit={onSubmit} collectionId={collectionId} {...props}>
|
||||
<BackToDashboard disabled={props.disableHomeLink}>
|
||||
<TextInput
|
||||
label={t('form.name')}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { ActionIcon, Anchor, CopyButton, Popover, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TbShare3 } from 'react-icons/tb';
|
||||
import { Fragment } from 'react/jsx-runtime';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { generateShareUrl } from '~/lib/navigation';
|
||||
import { useActiveCollection } from '~/stores/collection_store';
|
||||
import { Visibility } from '~/types/app';
|
||||
|
||||
const COPY_SUCCESS_TIMEOUT = 2_000;
|
||||
|
||||
export function ShareCollection() {
|
||||
const { t } = useTranslation('common');
|
||||
const { activeCollection } = useActiveCollection();
|
||||
const activeCollection = useActiveCollection();
|
||||
if (
|
||||
activeCollection?.visibility !== Visibility.PUBLIC ||
|
||||
typeof window === 'undefined'
|
||||
|
||||
Reference in New Issue
Block a user