diff --git a/app/user_settings/controllers/show_user_settings_controller.ts b/app/user_settings/controllers/show_user_settings_controller.ts
new file mode 100644
index 0000000..7fd5841
--- /dev/null
+++ b/app/user_settings/controllers/show_user_settings_controller.ts
@@ -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,
+ });
+ }
+}
diff --git a/app/user_settings/routes/routes.ts b/app/user_settings/routes/routes.ts
new file mode 100644
index 0000000..4f62e47
--- /dev/null
+++ b/app/user_settings/routes/routes.ts
@@ -0,0 +1 @@
+import './user_settings_routes.js';
diff --git a/app/user_settings/routes/user_settings_routes.ts b/app/user_settings/routes/user_settings_routes.ts
new file mode 100644
index 0000000..265a370
--- /dev/null
+++ b/app/user_settings/routes/user_settings_routes.ts
@@ -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');
diff --git a/inertia/components/common/floating_navbar/user_dropdown.tsx b/inertia/components/common/floating_navbar/user_dropdown.tsx
index acf37b4..c70037f 100644
--- a/inertia/components/common/floating_navbar/user_dropdown.tsx
+++ b/inertia/components/common/floating_navbar/user_dropdown.tsx
@@ -1,11 +1,9 @@
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, 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';
@@ -15,13 +13,6 @@ export function UserDropdown() {
useDisclosure(false);
const { t } = useTranslation();
- const handlePreferencesModal = () => {
- modals.open({
- title: t('user-preferences'),
- children: ,
- });
- };
-
return (
)}
- {t('common:settings')}
+ {t('common:user')}
}
- onClick={handlePreferencesModal}
+ component={InternalLinkUnstyled}
+ href="/user/settings"
>
- {t('common:preferences')}
+ {t('common:settings')}
}
diff --git a/inertia/components/common/floating_tabs/floating_tabs.module.css b/inertia/components/common/floating_tabs/floating_tabs.module.css
new file mode 100644
index 0000000..1b4eb02
--- /dev/null
+++ b/inertia/components/common/floating_tabs/floating_tabs.module.css
@@ -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);
+ }
+ }
+}
diff --git a/inertia/components/common/floating_tabs/floating_tabs.tsx b/inertia/components/common/floating_tabs/floating_tabs.tsx
new file mode 100644
index 0000000..b852b0e
--- /dev/null
+++ b/inertia/components/common/floating_tabs/floating_tabs.tsx
@@ -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(null);
+ const [value, setValue] = useState(tabs[0].value);
+ const [controlsRefs, setControlsRefs] = useState<
+ Record
+ >({});
+ const setControlRef = (val: string) => (node: HTMLButtonElement) => {
+ controlsRefs[val] = node;
+ setControlsRefs(controlsRefs);
+ };
+
+ return (
+
+
+ {tabs.map((tab) => (
+
+
+ {tab.label}
+
+
+ ))}
+
+
+ {tabs.map((tab) => (
+
+ {tab.content}
+
+ ))}
+
+ );
+}
diff --git a/inertia/components/common/user_preferences/user_preferences.tsx b/inertia/components/common/user_preferences/user_preferences.tsx
index a5c35bf..5f1e639 100644
--- a/inertia/components/common/user_preferences/user_preferences.tsx
+++ b/inertia/components/common/user_preferences/user_preferences.tsx
@@ -1,15 +1,10 @@
-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 { LinkListSelector } from '~/components/dashboard/link/link_list_selector';
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();
@@ -28,15 +23,7 @@ export function UserPreferences() {
{t('display-preferences.link-list-display')}
-
- handleUpdateDisplayPreferences({
- linkListDisplay: value as LinkListDisplay,
- })
- }
- />
+
);
diff --git a/inertia/components/dashboard/collection/collection_list_selector.tsx b/inertia/components/dashboard/collection/collection_list_selector.tsx
index f7bbacd..7347123 100644
--- a/inertia/components/dashboard/collection/collection_list_selector.tsx
+++ b/inertia/components/dashboard/collection/collection_list_selector.tsx
@@ -1,20 +1,28 @@
+import { COLLECTION_LIST_DISPLAYS } from '#shared/lib/display_preferences';
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 { getCollectionListDisplayOptions } from '~/lib/display_preferences';
export function CollectionListSelector() {
+ const { t } = useTranslation();
const { displayPreferences, handleUpdateDisplayPreferences } =
useDisplayPreferences();
+
+ const data = COLLECTION_LIST_DISPLAYS.map((display) => ({
+ label: t(`display-preferences.${display}`),
+ value: display,
+ }));
return (
-
+
handleUpdateDisplayPreferences({
collectionListDisplay: value as CollectionListDisplay,
})
}
+ w="50%"
/>
);
}
diff --git a/inertia/components/dashboard/link/link_list_selector.tsx b/inertia/components/dashboard/link/link_list_selector.tsx
new file mode 100644
index 0000000..dbda552
--- /dev/null
+++ b/inertia/components/dashboard/link/link_list_selector.tsx
@@ -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 (
+
+ handleUpdateDisplayPreferences({
+ linkListDisplay: value as LinkListDisplay,
+ })
+ }
+ w="50%"
+ />
+ );
+}
diff --git a/inertia/i18n/locales/en/common.json b/inertia/i18n/locales/en/common.json
index 6d7edee..e1bb1f3 100644
--- a/inertia/i18n/locales/en/common.json
+++ b/inertia/i18n/locales/en/common.json
@@ -89,7 +89,10 @@
"preferences-description": "Display preferences do not apply on mobile",
"display-preferences": {
"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"
}
diff --git a/inertia/i18n/locales/fr/common.json b/inertia/i18n/locales/fr/common.json
index d998637..66e1b3e 100644
--- a/inertia/i18n/locales/fr/common.json
+++ b/inertia/i18n/locales/fr/common.json
@@ -89,7 +89,10 @@
"preferences-description": "Les préférences d'affichage ne s'appliquent pas sur mobile",
"display-preferences": {
"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"
}
diff --git a/inertia/layouts/small_content.tsx b/inertia/layouts/small_content.tsx
index 5c371e2..842e5c8 100644
--- a/inertia/layouts/small_content.tsx
+++ b/inertia/layouts/small_content.tsx
@@ -12,7 +12,8 @@ const SmallContentLayout = ({ children }: PropsWithChildren) => (
export default SmallContentLayout;
-const LAYOUT_WIDTH = '800px';
+const LAYOUT_WIDTH = '1500px';
+const CONTENT_WIDTH = '800px';
const Layout = ({ children }: PropsWithChildren) => (
<>
{/* Top navbar */}
@@ -29,7 +30,7 @@ const Layout = ({ children }: PropsWithChildren) => (
style={{
height: '100%',
maxWidth: '100%',
- width: LAYOUT_WIDTH,
+ width: CONTENT_WIDTH,
marginInline: 'auto',
marginBlock: rem(30),
}}
diff --git a/inertia/lib/display_preferences.tsx b/inertia/lib/display_preferences.tsx
deleted file mode 100644
index 109148b..0000000
--- a/inertia/lib/display_preferences.tsx
+++ /dev/null
@@ -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: ,
- inline: ,
-} as const;
-
-export function getCollectionListDisplayOptions(): ValueWithIcon[] {
- return COLLECTION_LIST_DISPLAYS.map((display) => ({
- label: display,
- value: display,
- icon: collectionListDisplayIcons[display],
- }));
-}
-
-const linkListDisplayIcons = {
- list: ,
- grid: ,
-} as const;
-
-export function getLinkListDisplayOptions(): ValueWithIcon[] {
- return LINK_LIST_DISPLAYS.map((display) => ({
- label: display,
- value: display,
- icon: linkListDisplayIcons[display],
- }));
-}
diff --git a/inertia/pages/user_settings/show.tsx b/inertia/pages/user_settings/show.tsx
new file mode 100644
index 0000000..394f278
--- /dev/null
+++ b/inertia/pages/user_settings/show.tsx
@@ -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: ,
+ },
+ ];
+ return ;
+}
+
+UserSettingsShow.layout = (page: React.ReactNode) => (
+ {page}
+);
+export default UserSettingsShow;
diff --git a/package.json b/package.json
index 4b2e07d..7c81290 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"#search/*": "./app/search/*.js",
"#shared_collections/*": "./app/shared_collections/*.js",
"#user/*": "./app/user/*.js",
+ "#user_settings/*": "./app/user_settings/*.js",
"#providers/*": "./providers/*.js",
"#database/*": "./database/*.js",
"#tests/*": "./tests/*.js",
diff --git a/start/routes.ts b/start/routes.ts
index 93eb0e2..e2b8ca2 100644
--- a/start/routes.ts
+++ b/start/routes.ts
@@ -8,3 +8,4 @@ import '#links/routes/routes';
import '#search/routes/routes';
import '#shared_collections/routes/routes';
import '#user/routes/routes';
+import '#user_settings/routes/routes';