(frontend) wire CTAs into header and standalone file preview

UserProfile now renders AnonymousCTA along with a dropdown menu
(copy link, language picker) when no user is authenticated, so
visitors on a public page can still act on it without the user
menu. CustomFilesPreview gains a CONTEXTUAL mode that swaps in
the right CTA based on auth state, used by the standalone file
preview page which is now wrapped in GlobalLayout to expose the
auth context.
This commit is contained in:
Nathan Vasse
2026-06-04 11:16:40 +02:00
committed by Nicolas Clerc
parent 82e57aefc5
commit 4adbe9f2b7
4 changed files with 118 additions and 18 deletions
+4
View File
@@ -24,6 +24,10 @@ and this project adheres to
- 🔥(backend) drop deprecated numchild columns from item
### Added
- ✨(frontend) add CTA on public link for anonymous and authenticated users
## [v0.18.0] - 2026-05-04
### Added
@@ -1,8 +1,20 @@
import { UserMenu } from "@gouvfr-lasuite/ui-kit";
import {
DropdownMenu,
Icon,
IconSize,
useDropdownMenu,
UserMenu,
} from "@gouvfr-lasuite/ui-kit";
import { useAuth } from "@/features/auth/Auth";
import { logout } from "@/features/auth/Auth";
import { LanguagePickerUserMenu } from "@/features/layouts/components/header/Header";
import { LoginButton } from "@/features/auth/components/LoginButton";
import {
LanguagePickerUserMenu,
LANGUAGES,
} from "@/features/layouts/components/header/Header";
import { AnonymousCTA } from "../anonymous-cta/AnonymousCTA";
import { Button } from "@gouvfr-lasuite/cunningham-react";
import { useTranslation } from "react-i18next";
import { useClipboard } from "@/hooks/useCopyToClipboard";
export const UserProfile = () => {
const { user } = useAuth();
@@ -16,8 +28,50 @@ export const UserProfile = () => {
actions={<LanguagePickerUserMenu />}
/>
) : (
<LoginButton />
<>
<AnonymousDropdownMenu />
<AnonymousCTA />
</>
)}
</>
);
};
const AnonymousDropdownMenu = () => {
const { isOpen, setIsOpen } = useDropdownMenu();
const { t, i18n } = useTranslation();
const copyToClipboard = useClipboard();
return (
<DropdownMenu
isOpen={isOpen}
onOpenChange={setIsOpen}
options={[
{
icon: <Icon name="link" size={IconSize.SMALL} />,
label: t("anonymous_dropdown_menu.copy_link"),
callback: () => {
copyToClipboard(window.location.href);
},
},
{
icon: <Icon name="language" size={IconSize.SMALL} />,
label: t("anonymous_dropdown_menu.languages"),
children: LANGUAGES.map((language) => ({
label: language.label,
callback: () => {
i18n.changeLanguage(language.value);
},
})),
},
]}
>
<Button
icon={<Icon name="more_horiz" />}
variant="tertiary"
onClick={() => setIsOpen(!isOpen)}
data-testid="anonymous-dropdown-menu"
/>
</DropdownMenu>
);
};
@@ -1,7 +1,7 @@
import { Item, ItemType } from "@/features/drivers/types";
import { FilePreview, FilePreviewType } from "@gouvfr-lasuite/ui-kit";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
import { PropsWithChildren, useMemo } from "react";
import posthog from "posthog-js";
import { itemToPreviewFile } from "@/features/explorer/utils/utils";
import { useDownloadItem } from "@/features/items/hooks/useDownloadItem";
@@ -9,17 +9,29 @@ import { ItemInfo } from "@/features/items/components/ItemInfo";
import { Button, useModal } from "@gouvfr-lasuite/cunningham-react";
import { ItemShareModal } from "@/features/explorer/components/modals/share/ItemShareModal";
import { openWopiInNewTab } from "@/features/wopi/openWopi";
import { useAuth } from "@/features/auth/Auth";
import { AnonymousCTA } from "../components/anonymous-cta/AnonymousCTA";
import { MyFilesCTA } from "../components/my-files-cta/MyFilesCTA";
export enum CustomFilesPreviewMode {
// The actions header will be the default actions header.
DEFAULT = "default",
// The actions header will be contextual to the authentication status of the user.
CONTEXTUAL = "contextual",
}
type CustomFilesPreviewProps = {
currentItem?: Item;
items: Item[];
setPreviewItem?: (item?: Item) => void;
mode?: CustomFilesPreviewMode;
};
export const CustomFilesPreview = ({
currentItem,
items,
setPreviewItem,
mode = CustomFilesPreviewMode.DEFAULT,
}: CustomFilesPreviewProps) => {
const { handleDownloadItem } = useDownloadItem();
@@ -54,9 +66,11 @@ export const CustomFilesPreview = ({
})
}
onOpenInEditor={openWopiInNewTab}
headerRightContent={
<CustomFilesPreviewRightHeader currentItem={currentItem} />
}
customHeaderActions={(actions) => (
<CustomFilesPreviewRightHeader currentItem={currentItem} mode={mode}>
{actions}
</CustomFilesPreviewRightHeader>
)}
sidebarContent={currentItem && <ItemInfo item={currentItem} />}
/>
);
@@ -64,12 +78,16 @@ export const CustomFilesPreview = ({
type CustomFilesPreviewRightHeaderProps = {
currentItem?: Item;
};
mode: CustomFilesPreviewMode;
} & PropsWithChildren;
const CustomFilesPreviewRightHeader = ({
children,
currentItem,
mode,
}: CustomFilesPreviewRightHeaderProps) => {
const { t } = useTranslation();
const { user } = useAuth();
const shareModal = useModal();
if (!currentItem) {
@@ -78,14 +96,26 @@ const CustomFilesPreviewRightHeader = ({
return (
<>
<div className="custom-files-preview-right-header">
<Button variant="tertiary" onClick={shareModal.open}>
{t("explorer.rightPanel.share")}
</Button>
</div>
{mode === CustomFilesPreviewMode.DEFAULT && (
<>
<div className="custom-files-preview-right-header">
<Button variant="tertiary" onClick={shareModal.open}>
{t("explorer.rightPanel.share")}
</Button>
</div>
{shareModal.isOpen && (
<ItemShareModal {...shareModal} item={currentItem} />
{shareModal.isOpen && (
<ItemShareModal {...shareModal} item={currentItem} />
)}
</>
)}
{children}
{mode === CustomFilesPreviewMode.CONTEXTUAL && (
<div className="custom-files-preview-right-header">
{user ? <MyFilesCTA /> : <AnonymousCTA />}
</div>
)}
</>
);
@@ -1,11 +1,15 @@
import { GenericDisclaimer } from "@/features/ui/components/generic-disclaimer/GenericDisclaimer";
import { SpinnerPage } from "@/features/ui/components/spinner/SpinnerPage";
import { CustomFilesPreview } from "@/features/ui/preview/CustomFilesPreview";
import {
CustomFilesPreview,
CustomFilesPreviewMode,
} from "@/features/ui/preview/CustomFilesPreview";
import { Icon } from "@gouvfr-lasuite/ui-kit";
import { Button } from "@gouvfr-lasuite/cunningham-react";
import { useRouter } from "next/router";
import { useTranslation } from "react-i18next";
import { useItem } from "@/features/explorer/hooks/useQueries";
import { GlobalLayout } from "@/features/layouts/components/global/GlobalLayout";
export default function FilePage() {
const { t } = useTranslation();
@@ -37,7 +41,15 @@ export default function FilePage() {
return (
<div>
<CustomFilesPreview currentItem={item} items={[item]} />
<CustomFilesPreview
currentItem={item}
items={[item]}
mode={CustomFilesPreviewMode.CONTEXTUAL}
/>
</div>
);
}
FilePage.getLayout = function getLayout(page: React.ReactElement) {
return <GlobalLayout>{page}</GlobalLayout>;
};