mirror of
https://github.com/suitenumerique/drive.git
synced 2026-09-12 04:37:56 +02:00
✨(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:
@@ -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>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user