mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
Compare commits
2 Commits
44c187acaf
...
376e9e32c3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
376e9e32c3 | ||
|
|
c2a1d06008 |
@@ -0,0 +1,10 @@
|
|||||||
|
import { HttpContext } from '@adonisjs/core/http';
|
||||||
|
|
||||||
|
export default class ShowUserSettingsController {
|
||||||
|
public async render({ auth, inertia }: HttpContext) {
|
||||||
|
const user = await auth.authenticate();
|
||||||
|
return inertia.render('user_settings/show', {
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
1
app/user_settings/routes/routes.ts
Normal file
1
app/user_settings/routes/routes.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import './user_settings_routes.js';
|
||||||
8
app/user_settings/routes/user_settings_routes.ts
Normal file
8
app/user_settings/routes/user_settings_routes.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import router from '@adonisjs/core/services/router';
|
||||||
|
|
||||||
|
const ShowUserSettingsController = () =>
|
||||||
|
import('#user_settings/controllers/show_user_settings_controller');
|
||||||
|
|
||||||
|
router
|
||||||
|
.get('/user/settings', [ShowUserSettingsController, 'render'])
|
||||||
|
.as('user.settings');
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import { Avatar, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
import { Avatar, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { modals } from '@mantine/modals';
|
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TbChevronDown, TbLogout, TbSettings, TbShield } from 'react-icons/tb';
|
import { TbChevronDown, TbLogout, TbSettings, TbShield } from 'react-icons/tb';
|
||||||
import { InternalLinkUnstyled } from '~/components/common/links/internal_link_unstyled';
|
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 { useAuth } from '~/hooks/use_auth';
|
||||||
import classes from './user_dropdown.module.css';
|
import classes from './user_dropdown.module.css';
|
||||||
|
|
||||||
@@ -15,13 +13,6 @@ export function UserDropdown() {
|
|||||||
useDisclosure(false);
|
useDisclosure(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handlePreferencesModal = () => {
|
|
||||||
modals.open({
|
|
||||||
title: t('user-preferences'),
|
|
||||||
children: <UserPreferences />,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
width={260}
|
width={260}
|
||||||
@@ -64,12 +55,13 @@ export function UserDropdown() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Menu.Label>{t('common:settings')}</Menu.Label>
|
<Menu.Label>{t('common:user')}</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<TbSettings size={16} />}
|
leftSection={<TbSettings size={16} />}
|
||||||
onClick={handlePreferencesModal}
|
component={InternalLinkUnstyled}
|
||||||
|
href="/user/settings"
|
||||||
>
|
>
|
||||||
{t('common:preferences')}
|
{t('common:settings')}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={<TbLogout size={16} />}
|
leftSection={<TbLogout size={16} />}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
.list {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: var(--mantine-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
z-index: -1 !important;
|
||||||
|
background-color: var(--mantine-color-white);
|
||||||
|
border-radius: var(--mantine-radius-md);
|
||||||
|
border: 1px solid var(--mantine-color-gray-2);
|
||||||
|
box-shadow: var(--mantine-shadow-sm);
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-6);
|
||||||
|
border-color: var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
z-index: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 100ms ease;
|
||||||
|
color: var(--mantine-color-gray-7);
|
||||||
|
|
||||||
|
&[data-active] {
|
||||||
|
color: var(--mantine-color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
color: var(--mantine-color-dark-1);
|
||||||
|
|
||||||
|
&[data-active] {
|
||||||
|
color: var(--mantine-color-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
inertia/components/common/floating_tabs/floating_tabs.tsx
Normal file
79
inertia/components/common/floating_tabs/floating_tabs.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import {
|
||||||
|
FloatingIndicator,
|
||||||
|
Indicator,
|
||||||
|
Tabs as MantineTabs,
|
||||||
|
Stack,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import classes from './floating_tabs.module.css';
|
||||||
|
|
||||||
|
export type FloatingTab = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
content: React.ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
indicator?: {
|
||||||
|
content: string;
|
||||||
|
color?: string;
|
||||||
|
pulse?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface FloatingTabsProps {
|
||||||
|
tabs: FloatingTab[];
|
||||||
|
keepMounted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FloatingTabs({ tabs, keepMounted = false }: FloatingTabsProps) {
|
||||||
|
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
||||||
|
const [value, setValue] = useState<string | null>(tabs[0].value);
|
||||||
|
const [controlsRefs, setControlsRefs] = useState<
|
||||||
|
Record<string, HTMLButtonElement | null>
|
||||||
|
>({});
|
||||||
|
const setControlRef = (val: string) => (node: HTMLButtonElement) => {
|
||||||
|
controlsRefs[val] = node;
|
||||||
|
setControlsRefs(controlsRefs);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MantineTabs
|
||||||
|
variant="none"
|
||||||
|
value={value}
|
||||||
|
onChange={setValue}
|
||||||
|
keepMounted={keepMounted}
|
||||||
|
>
|
||||||
|
<MantineTabs.List ref={setRootRef} className={classes.list}>
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<Indicator
|
||||||
|
label={tab.indicator?.content}
|
||||||
|
color={tab.indicator?.color}
|
||||||
|
processing={tab.indicator?.pulse}
|
||||||
|
disabled={!tab.indicator || tab.indicator.disabled}
|
||||||
|
size={16}
|
||||||
|
key={tab.value}
|
||||||
|
>
|
||||||
|
<MantineTabs.Tab
|
||||||
|
value={tab.value}
|
||||||
|
ref={setControlRef(tab.value)}
|
||||||
|
className={classes.tab}
|
||||||
|
disabled={tab.disabled}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</MantineTabs.Tab>
|
||||||
|
</Indicator>
|
||||||
|
))}
|
||||||
|
<FloatingIndicator
|
||||||
|
target={value ? controlsRefs[value] : null}
|
||||||
|
parent={rootRef}
|
||||||
|
className={classes.indicator}
|
||||||
|
/>
|
||||||
|
</MantineTabs.List>
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<MantineTabs.Panel key={tab.value} value={tab.value}>
|
||||||
|
<Stack>{tab.content}</Stack>
|
||||||
|
</MantineTabs.Panel>
|
||||||
|
))}
|
||||||
|
</MantineTabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,33 +1,38 @@
|
|||||||
import { AUTHOR_GITHUB_URL, AUTHOR_NAME } from '#config/project';
|
import { AUTHOR_GITHUB_URL, AUTHOR_NAME } from '#config/project';
|
||||||
import PATHS from '#core/constants/paths';
|
import PATHS from '#core/constants/paths';
|
||||||
|
import { route } from '@izzyjs/route/client';
|
||||||
import { Anchor, Group, Text } from '@mantine/core';
|
import { Anchor, Group, Text } from '@mantine/core';
|
||||||
|
import { withTranslation, WithTranslation } from 'react-i18next';
|
||||||
import ExternalLink from '~/components/common/external_link';
|
import ExternalLink from '~/components/common/external_link';
|
||||||
import { ExternalLinkStyled } from '~/components/common/links/external_link_styled';
|
import { ExternalLinkStyled } from '~/components/common/links/external_link_styled';
|
||||||
|
import { InternalLink } from '~/components/common/links/internal_link';
|
||||||
import { LocaleSwitcher } from '~/components/common/locale_switcher';
|
import { LocaleSwitcher } from '~/components/common/locale_switcher';
|
||||||
import { ThemeSwitcher } from '~/components/common/theme_switcher';
|
import { ThemeSwitcher } from '~/components/common/theme_switcher';
|
||||||
import packageJson from '../../../../package.json';
|
import packageJson from '../../../../package.json';
|
||||||
import classes from './footer.module.css';
|
import classes from './footer.module.css';
|
||||||
|
|
||||||
export const Footer = () => (
|
export const Footer = withTranslation()(({ t }: WithTranslation) => (
|
||||||
<Group className={classes.footer}>
|
<Group className={classes.footer}>
|
||||||
<Group className={classes.footer__content}>
|
<Group className={classes.footer__content}>
|
||||||
<Text>
|
<Text>
|
||||||
Made with ❤️ by{' '}
|
{t('footer.made_by')}{' '}
|
||||||
<ExternalLinkStyled href={AUTHOR_GITHUB_URL}>
|
<ExternalLinkStyled href={AUTHOR_GITHUB_URL}>
|
||||||
{AUTHOR_NAME}
|
{AUTHOR_NAME}
|
||||||
</ExternalLinkStyled>
|
</ExternalLinkStyled>
|
||||||
</Text>
|
</Text>
|
||||||
•
|
•
|
||||||
<Text>
|
<Group gap="sm">
|
||||||
<Anchor size="sm" component={ExternalLink} href={PATHS.REPO_GITHUB}>
|
|
||||||
{packageJson.version}
|
|
||||||
</Anchor>
|
|
||||||
</Text>
|
|
||||||
•
|
|
||||||
<Group gap="sm" mt={4} mb={4}>
|
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
<LocaleSwitcher />
|
<LocaleSwitcher />
|
||||||
</Group>
|
</Group>
|
||||||
|
•
|
||||||
|
<Group gap="sm">
|
||||||
|
<Anchor size="sm" component={ExternalLink} href={PATHS.REPO_GITHUB}>
|
||||||
|
{packageJson.version}
|
||||||
|
</Anchor>
|
||||||
|
<InternalLink href={route('privacy').path}>{t('privacy')}</InternalLink>
|
||||||
|
<InternalLink href={route('terms').path}>{t('terms')}</InternalLink>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
));
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import { LinkListDisplay } from '#shared/types/index';
|
|
||||||
import { Fieldset, Stack, Text } from '@mantine/core';
|
import { Fieldset, Stack, Text } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ComboList } from '~/components/common/combo_list/combo_list';
|
|
||||||
import { CollectionListSelector } from '~/components/dashboard/collection/collection_list_selector';
|
import { CollectionListSelector } from '~/components/dashboard/collection/collection_list_selector';
|
||||||
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
import { LinkListSelector } from '~/components/dashboard/link/link_list_selector';
|
||||||
import { useIsMobile } from '~/hooks/use_is_mobile';
|
import { useIsMobile } from '~/hooks/use_is_mobile';
|
||||||
import { getLinkListDisplayOptions } from '~/lib/display_preferences';
|
|
||||||
|
|
||||||
export function UserPreferences() {
|
export function UserPreferences() {
|
||||||
const { displayPreferences, handleUpdateDisplayPreferences } =
|
|
||||||
useDisplayPreferences();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
@@ -28,15 +23,7 @@ export function UserPreferences() {
|
|||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{t('display-preferences.link-list-display')}
|
{t('display-preferences.link-list-display')}
|
||||||
</Text>
|
</Text>
|
||||||
<ComboList
|
<LinkListSelector />
|
||||||
selectedValue={displayPreferences.linkListDisplay}
|
|
||||||
values={getLinkListDisplayOptions()}
|
|
||||||
setValue={(value) =>
|
|
||||||
handleUpdateDisplayPreferences({
|
|
||||||
linkListDisplay: value as LinkListDisplay,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
|
import { COLLECTION_LIST_DISPLAYS } from '#shared/lib/display_preferences';
|
||||||
import { CollectionListDisplay } from '#shared/types/index';
|
import { CollectionListDisplay } from '#shared/types/index';
|
||||||
import { ComboList } from '~/components/common/combo_list/combo_list';
|
import { SegmentedControl } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
||||||
import { getCollectionListDisplayOptions } from '~/lib/display_preferences';
|
|
||||||
|
|
||||||
export function CollectionListSelector() {
|
export function CollectionListSelector() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { displayPreferences, handleUpdateDisplayPreferences } =
|
const { displayPreferences, handleUpdateDisplayPreferences } =
|
||||||
useDisplayPreferences();
|
useDisplayPreferences();
|
||||||
|
|
||||||
|
const data = COLLECTION_LIST_DISPLAYS.map((display) => ({
|
||||||
|
label: t(`display-preferences.${display}`),
|
||||||
|
value: display,
|
||||||
|
}));
|
||||||
return (
|
return (
|
||||||
<ComboList
|
<SegmentedControl
|
||||||
selectedValue={displayPreferences.collectionListDisplay}
|
data={data}
|
||||||
values={getCollectionListDisplayOptions()}
|
value={displayPreferences.collectionListDisplay}
|
||||||
setValue={(value) =>
|
onChange={(value) =>
|
||||||
handleUpdateDisplayPreferences({
|
handleUpdateDisplayPreferences({
|
||||||
collectionListDisplay: value as CollectionListDisplay,
|
collectionListDisplay: value as CollectionListDisplay,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
w="50%"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
28
inertia/components/dashboard/link/link_list_selector.tsx
Normal file
28
inertia/components/dashboard/link/link_list_selector.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { LINK_LIST_DISPLAYS } from '#shared/lib/display_preferences';
|
||||||
|
import { LinkListDisplay } from '#shared/types/index';
|
||||||
|
import { SegmentedControl } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
||||||
|
|
||||||
|
export function LinkListSelector() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { displayPreferences, handleUpdateDisplayPreferences } =
|
||||||
|
useDisplayPreferences();
|
||||||
|
|
||||||
|
const data = LINK_LIST_DISPLAYS.map((display) => ({
|
||||||
|
label: t(`display-preferences.${display}`),
|
||||||
|
value: display,
|
||||||
|
}));
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
data={data}
|
||||||
|
value={displayPreferences.linkListDisplay}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleUpdateDisplayPreferences({
|
||||||
|
linkListDisplay: value as LinkListDisplay,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
w="50%"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
.footer {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inner {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@media (max-width: $mantine-breakpoint-xs) {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
|
||||||
@media (max-width: $mantine-breakpoint-xs) {
|
|
||||||
margin-top: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import PATHS from '#core/constants/paths';
|
|
||||||
import { Link } from '@inertiajs/react';
|
|
||||||
import { route } from '@izzyjs/route/client';
|
|
||||||
import { Anchor, Group, Text } from '@mantine/core';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ExternalLink from '~/components/common/external_link';
|
|
||||||
import { LocaleSwitcher } from '~/components/common/locale_switcher';
|
|
||||||
import { ThemeSwitcher } from '~/components/common/theme_switcher';
|
|
||||||
import packageJson from '../../../package.json';
|
|
||||||
import classes from './footer.module.css';
|
|
||||||
|
|
||||||
export function MantineFooter() {
|
|
||||||
const { t } = useTranslation('common');
|
|
||||||
|
|
||||||
const links = [
|
|
||||||
{ link: route('privacy').path, label: t('privacy'), external: false },
|
|
||||||
{ link: route('terms').path, label: t('terms'), external: false },
|
|
||||||
{ link: PATHS.EXTENSION, label: 'Extension', external: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
const items = links.map((link) => (
|
|
||||||
<Anchor
|
|
||||||
c="dimmed"
|
|
||||||
// @ts-expect-error
|
|
||||||
component={link.external ? ExternalLink : Link}
|
|
||||||
key={link.label}
|
|
||||||
href={link.link}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{link.label}
|
|
||||||
</Anchor>
|
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.footer}>
|
|
||||||
<div className={classes.inner}>
|
|
||||||
<Group gap={4} c="dimmed">
|
|
||||||
<Text size="sm">{t('footer.made_by')}</Text>{' '}
|
|
||||||
<Anchor size="sm" component={ExternalLink} href={PATHS.AUTHOR}>
|
|
||||||
Sonny
|
|
||||||
</Anchor>
|
|
||||||
{' • '}
|
|
||||||
<Anchor size="sm" component={ExternalLink} href={PATHS.REPO_GITHUB}>
|
|
||||||
{packageJson.version}
|
|
||||||
</Anchor>
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<Group gap="sm" mt={4} mb={4}>
|
|
||||||
<ThemeSwitcher />
|
|
||||||
<LocaleSwitcher />
|
|
||||||
</Group>
|
|
||||||
|
|
||||||
<Group gap="xs" justify="flex-end" wrap="nowrap">
|
|
||||||
{items}
|
|
||||||
</Group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -89,7 +89,10 @@
|
|||||||
"preferences-description": "Display preferences do not apply on mobile",
|
"preferences-description": "Display preferences do not apply on mobile",
|
||||||
"display-preferences": {
|
"display-preferences": {
|
||||||
"collection-list-display": "Collection list display",
|
"collection-list-display": "Collection list display",
|
||||||
"link-list-display": "Link list display"
|
"link-list-display": "Link list display",
|
||||||
|
"inline": "Inline",
|
||||||
|
"list": "List",
|
||||||
|
"grid": "Grid"
|
||||||
},
|
},
|
||||||
"coming-soon": "Under development"
|
"coming-soon": "Under development"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,10 @@
|
|||||||
"preferences-description": "Les préférences d'affichage ne s'appliquent pas sur mobile",
|
"preferences-description": "Les préférences d'affichage ne s'appliquent pas sur mobile",
|
||||||
"display-preferences": {
|
"display-preferences": {
|
||||||
"collection-list-display": "Affichage de la liste des collections",
|
"collection-list-display": "Affichage de la liste des collections",
|
||||||
"link-list-display": "Affichage de la liste des liens"
|
"link-list-display": "Affichage de la liste des liens",
|
||||||
|
"inline": "En ligne",
|
||||||
|
"list": "Liste",
|
||||||
|
"grid": "Grille"
|
||||||
},
|
},
|
||||||
"coming-soon": "En cours de développement"
|
"coming-soon": "En cours de développement"
|
||||||
}
|
}
|
||||||
|
|||||||
45
inertia/layouts/small_content.tsx
Normal file
45
inertia/layouts/small_content.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Box, rem } from '@mantine/core';
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { FloatingNavbar } from '~/components/common/floating_navbar/floating_navbar';
|
||||||
|
import { Footer } from '~/components/common/footer/footer';
|
||||||
|
import { BaseLayout } from './_base_layout';
|
||||||
|
|
||||||
|
const SmallContentLayout = ({ children }: PropsWithChildren) => (
|
||||||
|
<BaseLayout>
|
||||||
|
<Layout>{children}</Layout>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SmallContentLayout;
|
||||||
|
|
||||||
|
const LAYOUT_WIDTH = '1500px';
|
||||||
|
const CONTENT_WIDTH = '800px';
|
||||||
|
const Layout = ({ children }: PropsWithChildren) => (
|
||||||
|
<>
|
||||||
|
{/* Top navbar */}
|
||||||
|
<FloatingNavbar width={LAYOUT_WIDTH} />
|
||||||
|
|
||||||
|
{/* Page content */}
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
paddingInline: 'var(--mantine-spacing-lg)',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
width: CONTENT_WIDTH,
|
||||||
|
marginInline: 'auto',
|
||||||
|
marginBlock: rem(30),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
);
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import {
|
|
||||||
COLLECTION_LIST_DISPLAYS,
|
|
||||||
LINK_LIST_DISPLAYS,
|
|
||||||
} from '#shared/lib/display_preferences';
|
|
||||||
import { AiOutlineFolder } from 'react-icons/ai';
|
|
||||||
import { IoGridOutline } from 'react-icons/io5';
|
|
||||||
import { TbList } from 'react-icons/tb';
|
|
||||||
import { ValueWithIcon } from '~/components/common/combo_list/combo_list';
|
|
||||||
|
|
||||||
const collectionListDisplayIcons = {
|
|
||||||
list: <TbList size={20} />,
|
|
||||||
inline: <AiOutlineFolder size={20} />,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export function getCollectionListDisplayOptions(): ValueWithIcon[] {
|
|
||||||
return COLLECTION_LIST_DISPLAYS.map((display) => ({
|
|
||||||
label: display,
|
|
||||||
value: display,
|
|
||||||
icon: collectionListDisplayIcons[display],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkListDisplayIcons = {
|
|
||||||
list: <TbList size={20} />,
|
|
||||||
grid: <IoGridOutline size={20} />,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export function getLinkListDisplayOptions(): ValueWithIcon[] {
|
|
||||||
return LINK_LIST_DISPLAYS.map((display) => ({
|
|
||||||
label: display,
|
|
||||||
value: display,
|
|
||||||
icon: linkListDisplayIcons[display],
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import SmallContentLayout from '~/layouts/small_content';
|
||||||
|
|
||||||
export default function PrivacyPage() {
|
function PrivacyPage() {
|
||||||
const { t } = useTranslation('privacy');
|
const { t } = useTranslation('privacy');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -41,3 +42,8 @@ export default function PrivacyPage() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrivacyPage.layout = (page: React.ReactNode) => (
|
||||||
|
<SmallContentLayout>{page}</SmallContentLayout>
|
||||||
|
);
|
||||||
|
export default PrivacyPage;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Link } from '@inertiajs/react';
|
import { Link } from '@inertiajs/react';
|
||||||
import { route } from '@izzyjs/route/client';
|
import { route } from '@izzyjs/route/client';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import SmallContentLayout from '~/layouts/small_content';
|
||||||
|
|
||||||
export default function TermsPage() {
|
function TermsPage() {
|
||||||
const { t } = useTranslation('terms');
|
const { t } = useTranslation('terms');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -28,7 +29,7 @@ export default function TermsPage() {
|
|||||||
<p>
|
<p>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="personal_data.collect.description"
|
i18nKey="personal_data.collect.description"
|
||||||
components={{ a: <Link href={route('privacy').url} /> }}
|
components={{ a: <Link href={route('privacy').path} /> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -50,3 +51,8 @@ export default function TermsPage() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TermsPage.layout = (page: React.ReactNode) => (
|
||||||
|
<SmallContentLayout>{page}</SmallContentLayout>
|
||||||
|
);
|
||||||
|
export default TermsPage;
|
||||||
|
|||||||
24
inertia/pages/user_settings/show.tsx
Normal file
24
inertia/pages/user_settings/show.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
FloatingTab,
|
||||||
|
FloatingTabs,
|
||||||
|
} from '~/components/common/floating_tabs/floating_tabs';
|
||||||
|
import { UserPreferences } from '~/components/common/user_preferences/user_preferences';
|
||||||
|
import SmallContentLayout from '~/layouts/small_content';
|
||||||
|
|
||||||
|
function UserSettingsShow() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const tabs: FloatingTab[] = [
|
||||||
|
{
|
||||||
|
label: t('preferences'),
|
||||||
|
value: 'preferences',
|
||||||
|
content: <UserPreferences />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return <FloatingTabs tabs={tabs} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserSettingsShow.layout = (page: React.ReactNode) => (
|
||||||
|
<SmallContentLayout>{page}</SmallContentLayout>
|
||||||
|
);
|
||||||
|
export default UserSettingsShow;
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
"#search/*": "./app/search/*.js",
|
"#search/*": "./app/search/*.js",
|
||||||
"#shared_collections/*": "./app/shared_collections/*.js",
|
"#shared_collections/*": "./app/shared_collections/*.js",
|
||||||
"#user/*": "./app/user/*.js",
|
"#user/*": "./app/user/*.js",
|
||||||
|
"#user_settings/*": "./app/user_settings/*.js",
|
||||||
"#providers/*": "./providers/*.js",
|
"#providers/*": "./providers/*.js",
|
||||||
"#database/*": "./database/*.js",
|
"#database/*": "./database/*.js",
|
||||||
"#tests/*": "./tests/*.js",
|
"#tests/*": "./tests/*.js",
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ import '#links/routes/routes';
|
|||||||
import '#search/routes/routes';
|
import '#search/routes/routes';
|
||||||
import '#shared_collections/routes/routes';
|
import '#shared_collections/routes/routes';
|
||||||
import '#user/routes/routes';
|
import '#user/routes/routes';
|
||||||
|
import '#user_settings/routes/routes';
|
||||||
|
|||||||
Reference in New Issue
Block a user