feat(homepage): add reset to default template functionality and update home page settings option (#14202)

This commit is contained in:
purnimagarg1 2025-07-24 23:38:20 +05:30 committed by GitHub
parent bcee2a4fb7
commit b1ef86b83e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 254 additions and 69 deletions

View File

@ -102,6 +102,9 @@ public class MeResolver implements DataFetcher<CompletableFuture<AuthenticatedUs
platformPrivileges.setManageApplications( platformPrivileges.setManageApplications(
ApplicationAuthorizationUtils.canManageApplications(context)); ApplicationAuthorizationUtils.canManageApplications(context));
platformPrivileges.setManageFeatures(AuthorizationUtils.canManageFeatures(context)); platformPrivileges.setManageFeatures(AuthorizationUtils.canManageFeatures(context));
platformPrivileges.setManageHomePageTemplates(
AuthorizationUtils.canManageHomePageTemplates(context));
// Construct and return authenticated user object. // Construct and return authenticated user object.
final AuthenticatedUser authUser = new AuthenticatedUser(); final AuthenticatedUser authUser = new AuthenticatedUser();
authUser.setCorpUser(corpUser); authUser.setCorpUser(corpUser);

View File

@ -86,7 +86,16 @@ public class UpdateUserHomePageSettingsResolver implements DataFetcher<Completab
@Nonnull final CorpUserHomePageSettings settings, @Nonnull final CorpUserHomePageSettings settings,
@Nonnull final UpdateUserHomePageSettingsInput input) { @Nonnull final UpdateUserHomePageSettingsInput input) {
if (input.getPageTemplate() != null) { Boolean shouldRemoveTemplate = input.getRemovePageTemplate();
if (input.getPageTemplate() != null && Boolean.TRUE.equals(shouldRemoveTemplate)) {
throw new IllegalArgumentException(
"Invalid inputs: Cannot specify both pageTemplate and removePageTemplate.");
}
if (Boolean.TRUE.equals(shouldRemoveTemplate)) {
settings.data().remove("pageTemplate");
} else if (input.getPageTemplate() != null) {
settings.setPageTemplate(UrnUtils.getUrn(input.getPageTemplate())); settings.setPageTemplate(UrnUtils.getUrn(input.getPageTemplate()));
} }

View File

@ -169,9 +169,11 @@ public class CorpUserMapper {
@Nonnull final com.linkedin.identity.CorpUserHomePageSettings homePageSettings) { @Nonnull final com.linkedin.identity.CorpUserHomePageSettings homePageSettings) {
CorpUserHomePageSettings result = new CorpUserHomePageSettings(); CorpUserHomePageSettings result = new CorpUserHomePageSettings();
if (homePageSettings.hasPageTemplate()) { if (homePageSettings.getPageTemplate() != null) {
result.setPageTemplate( result.setPageTemplate(
(DataHubPageTemplate) UrnToEntityMapper.map(null, homePageSettings.getPageTemplate())); (DataHubPageTemplate) UrnToEntityMapper.map(null, homePageSettings.getPageTemplate()));
} else {
result.setPageTemplate(null);
} }
if (homePageSettings.hasDismissedAnnouncements()) { if (homePageSettings.hasDismissedAnnouncements()) {

View File

@ -192,6 +192,11 @@ type PlatformPrivileges {
Whether the user can manage platform features. Whether the user can manage platform features.
""" """
manageFeatures: Boolean! manageFeatures: Boolean!
"""
Whether the user can manage default home page template.
"""
manageHomePageTemplates: Boolean!
} }
""" """

View File

@ -12503,6 +12503,11 @@ input UpdateUserHomePageSettingsInput {
The list of urns of announcement posts dismissed by the user. The list of urns of announcement posts dismissed by the user.
""" """
newDismissedAnnouncements: [String] newDismissedAnnouncements: [String]
"""
Whether to remove the page template for the user.
"""
removePageTemplate: Boolean
} }
""" """

View File

@ -3741,6 +3741,7 @@ export const mocks = [
viewStructuredPropertiesPage: true, viewStructuredPropertiesPage: true,
manageApplications: true, manageApplications: true,
manageFeatures: true, manageFeatures: true,
manageHomePageTemplates: true,
}, },
}, },
}, },
@ -4027,6 +4028,7 @@ export const platformPrivileges: PlatformPrivileges = {
viewStructuredPropertiesPage: true, viewStructuredPropertiesPage: true,
manageApplications: true, manageApplications: true,
manageFeatures: true, manageFeatures: true,
manageHomePageTemplates: true,
}; };
export const DomainMock1 = { export const DomainMock1 = {

View File

@ -89,6 +89,7 @@ describe('handleAccessRoles', () => {
viewStructuredPropertiesPage: true, viewStructuredPropertiesPage: true,
manageApplications: true, manageApplications: true,
manageFeatures: true, manageFeatures: true,
manageHomePageTemplates: true,
__typename: 'PlatformPrivileges', __typename: 'PlatformPrivileges',
}, },
__typename: 'AuthenticatedUser', __typename: 'AuthenticatedUser',
@ -174,6 +175,7 @@ describe('handleAccessRoles', () => {
viewStructuredPropertiesPage: true, viewStructuredPropertiesPage: true,
manageApplications: true, manageApplications: true,
manageFeatures: true, manageFeatures: true,
manageHomePageTemplates: true,
__typename: 'PlatformPrivileges', __typename: 'PlatformPrivileges',
}, },
__typename: 'AuthenticatedUser', __typename: 'AuthenticatedUser',
@ -267,6 +269,7 @@ describe('handleAccessRoles', () => {
viewStructuredPropertiesPage: true, viewStructuredPropertiesPage: true,
manageApplications: true, manageApplications: true,
manageFeatures: true, manageFeatures: true,
manageHomePageTemplates: true,
__typename: 'PlatformPrivileges', __typename: 'PlatformPrivileges',
}, },
__typename: 'AuthenticatedUser', __typename: 'AuthenticatedUser',

View File

@ -1,38 +0,0 @@
import { Button, Tooltip } from '@components';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import analytics, { EventType } from '@app/analytics';
import { usePageTemplateContext } from '@app/homeV3/context/PageTemplateContext';
const ButtonWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin-right: 42px;
`;
export default function EditDefaultTemplateButton() {
const { setIsEditingGlobalTemplate, isEditingGlobalTemplate } = usePageTemplateContext();
const onClick = useCallback(() => {
setIsEditingGlobalTemplate(true);
analytics.event({
type: EventType.HomePageTemplateGlobalTemplateEditingStart,
});
}, [setIsEditingGlobalTemplate]);
// TODO: also hide this if you don't have permissions - CH-510
if (isEditingGlobalTemplate) return null;
return (
<ButtonWrapper>
<Tooltip title="Edit the home page that users see by default">
<Button
icon={{ icon: 'PencilSimpleLine', color: 'gray', source: 'phosphor' }}
variant="text"
onClick={onClick}
/>
</Tooltip>
</ButtonWrapper>
);
}

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import EditDefaultTemplateBar from '@app/homeV3/EditDefaultTemplateBar';
import EditDefaultTemplateButton from '@app/homeV3/EditDefaultTemplateButton';
import { Announcements } from '@app/homeV3/announcements/Announcements'; import { Announcements } from '@app/homeV3/announcements/Announcements';
import EditDefaultTemplateBar from '@app/homeV3/settings/EditDefaultTemplateBar';
import EditHomePageSettingsButton from '@app/homeV3/settings/EditHomePageSettingsButton';
import { CenteredContainer, ContentContainer, ContentDiv } from '@app/homeV3/styledComponents'; import { CenteredContainer, ContentContainer, ContentDiv } from '@app/homeV3/styledComponents';
import Template from '@app/homeV3/template/Template'; import Template from '@app/homeV3/template/Template';
@ -11,7 +11,7 @@ const HomePageContent = () => {
<ContentContainer> <ContentContainer>
<CenteredContainer> <CenteredContainer>
<ContentDiv> <ContentDiv>
<EditDefaultTemplateButton /> <EditHomePageSettingsButton />
<Announcements /> <Announcements />
<Template /> <Template />
<EditDefaultTemplateBar /> <EditDefaultTemplateBar />

View File

@ -22,7 +22,8 @@ export const PageTemplateProvider = ({ children }: { children: ReactNode }) => {
} = useTemplateState(); } = useTemplateState();
// Template operations // Template operations
const { updateTemplateWithModule, removeModuleFromTemplate, upsertTemplate } = useTemplateOperations(); const { updateTemplateWithModule, removeModuleFromTemplate, upsertTemplate, resetTemplateToDefault } =
useTemplateOperations(setPersonalTemplate);
// Modal state // Modal state
const moduleModalState = useModuleModalState(); const moduleModalState = useModuleModalState();
@ -56,6 +57,7 @@ export const PageTemplateProvider = ({ children }: { children: ReactNode }) => {
upsertModule, upsertModule,
moduleModalState, moduleModalState,
moveModule, moveModule,
resetTemplateToDefault,
}), }),
[ [
personalTemplate, personalTemplate,
@ -71,6 +73,7 @@ export const PageTemplateProvider = ({ children }: { children: ReactNode }) => {
upsertModule, upsertModule,
moduleModalState, moduleModalState,
moveModule, moveModule,
resetTemplateToDefault,
], ],
); );

View File

@ -92,6 +92,7 @@ const mockMoveModule = vi.fn();
const mockUpdateTemplateWithModule = vi.fn(); const mockUpdateTemplateWithModule = vi.fn();
const mockRemoveModuleFromTemplate = vi.fn(); const mockRemoveModuleFromTemplate = vi.fn();
const mockUpsertTemplate = vi.fn(); const mockUpsertTemplate = vi.fn();
const mockResetTemplateToDefault = vi.fn();
describe('PageTemplateContext', () => { describe('PageTemplateContext', () => {
beforeEach(() => { beforeEach(() => {
@ -113,6 +114,7 @@ describe('PageTemplateContext', () => {
updateTemplateWithModule: mockUpdateTemplateWithModule, updateTemplateWithModule: mockUpdateTemplateWithModule,
removeModuleFromTemplate: mockRemoveModuleFromTemplate, removeModuleFromTemplate: mockRemoveModuleFromTemplate,
upsertTemplate: mockUpsertTemplate, upsertTemplate: mockUpsertTemplate,
resetTemplateToDefault: mockResetTemplateToDefault,
}); });
mockUseModuleOperations.mockReturnValue({ mockUseModuleOperations.mockReturnValue({

View File

@ -52,6 +52,8 @@ const mockModule: PageModuleFragment = {
}, },
}; };
const setPersonalTemplate = vi.fn();
describe('useTemplateOperations', () => { describe('useTemplateOperations', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
@ -61,7 +63,7 @@ describe('useTemplateOperations', () => {
describe('updateTemplateWithModule', () => { describe('updateTemplateWithModule', () => {
it('should add module to new row when rowIndex is undefined', () => { it('should add module to new row when rowIndex is undefined', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: undefined, rowIndex: undefined,
@ -77,7 +79,7 @@ describe('useTemplateOperations', () => {
}); });
it('should add module to existing row on the left side', () => { it('should add module to existing row on the left side', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 0, rowIndex: 0,
@ -94,7 +96,7 @@ describe('useTemplateOperations', () => {
}); });
it('should add module to existing row on the right side', () => { it('should add module to existing row on the right side', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 0, rowIndex: 0,
@ -111,7 +113,7 @@ describe('useTemplateOperations', () => {
}); });
it('should create new row when rowIndex is out of bounds', () => { it('should create new row when rowIndex is out of bounds', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 5, rowIndex: 5,
@ -127,7 +129,7 @@ describe('useTemplateOperations', () => {
}); });
it('should handle template with no rows', () => { it('should handle template with no rows', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithoutRows: PageTemplateFragment = { const templateWithoutRows: PageTemplateFragment = {
urn: 'urn:li:pageTemplate:empty', urn: 'urn:li:pageTemplate:empty',
@ -162,7 +164,7 @@ describe('useTemplateOperations', () => {
}); });
it('should return null when template is null', () => { it('should return null when template is null', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 0, rowIndex: 0,
@ -177,7 +179,7 @@ describe('useTemplateOperations', () => {
describe('removeModuleFromTemplate', () => { describe('removeModuleFromTemplate', () => {
it('should remove module by moduleIndex when provided', () => { it('should remove module by moduleIndex when provided', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithMultipleModules: PageTemplateFragment = { const templateWithMultipleModules: PageTemplateFragment = {
...mockTemplate, ...mockTemplate,
@ -231,7 +233,7 @@ describe('useTemplateOperations', () => {
}); });
it('should handle duplicate URNs correctly with moduleIndex', () => { it('should handle duplicate URNs correctly with moduleIndex', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithDuplicateUrns: PageTemplateFragment = { const templateWithDuplicateUrns: PageTemplateFragment = {
...mockTemplate, ...mockTemplate,
@ -297,7 +299,7 @@ describe('useTemplateOperations', () => {
}); });
it('should fall back to URN search when moduleIndex is invalid', () => { it('should fall back to URN search when moduleIndex is invalid', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithMultipleModules: PageTemplateFragment = { const templateWithMultipleModules: PageTemplateFragment = {
...mockTemplate, ...mockTemplate,
@ -351,7 +353,7 @@ describe('useTemplateOperations', () => {
}); });
it('should fall back to URN search when moduleIndex URN does not match', () => { it('should fall back to URN search when moduleIndex URN does not match', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithMultipleModules: PageTemplateFragment = { const templateWithMultipleModules: PageTemplateFragment = {
...mockTemplate, ...mockTemplate,
@ -405,7 +407,7 @@ describe('useTemplateOperations', () => {
}); });
it('should remove entire row when last module is removed', () => { it('should remove entire row when last module is removed', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithSingleModule: PageTemplateFragment = { const templateWithSingleModule: PageTemplateFragment = {
...mockTemplate, ...mockTemplate,
@ -462,7 +464,7 @@ describe('useTemplateOperations', () => {
}); });
it('should return original template when module is not found', () => { it('should return original template when module is not found', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 0, rowIndex: 0,
@ -480,7 +482,7 @@ describe('useTemplateOperations', () => {
}); });
it('should return original template when rowIndex is invalid', () => { it('should return original template when rowIndex is invalid', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 5, // Invalid index rowIndex: 5, // Invalid index
@ -498,7 +500,7 @@ describe('useTemplateOperations', () => {
}); });
it('should return original template when rowIndex is undefined', () => { it('should return original template when rowIndex is undefined', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: undefined, rowIndex: undefined,
@ -516,7 +518,7 @@ describe('useTemplateOperations', () => {
}); });
it('should return original template when template is null', () => { it('should return original template when template is null', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const position: ModulePositionInput = { const position: ModulePositionInput = {
rowIndex: 0, rowIndex: 0,
@ -530,7 +532,7 @@ describe('useTemplateOperations', () => {
}); });
it('should handle edge case with no modules in row', () => { it('should handle edge case with no modules in row', () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithEmptyRow: PageTemplateFragment = { const templateWithEmptyRow: PageTemplateFragment = {
...mockTemplate, ...mockTemplate,
@ -562,7 +564,7 @@ describe('useTemplateOperations', () => {
describe('upsertTemplate', () => { describe('upsertTemplate', () => {
it('should upsert personal template with correct input', async () => { it('should upsert personal template with correct input', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
mockUpsertPageTemplateMutation.mockResolvedValue({ mockUpsertPageTemplateMutation.mockResolvedValue({
data: { data: {
@ -593,7 +595,7 @@ describe('useTemplateOperations', () => {
}); });
it('should upsert existing personal template with URN', async () => { it('should upsert existing personal template with URN', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
mockUpsertPageTemplateMutation.mockResolvedValue({ mockUpsertPageTemplateMutation.mockResolvedValue({
data: { data: {
@ -624,7 +626,7 @@ describe('useTemplateOperations', () => {
}); });
it('should upsert global template with correct input', async () => { it('should upsert global template with correct input', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
mockUpsertPageTemplateMutation.mockResolvedValue({ mockUpsertPageTemplateMutation.mockResolvedValue({
data: { data: {
@ -655,7 +657,7 @@ describe('useTemplateOperations', () => {
}); });
it('should update user settings when creating personal template', async () => { it('should update user settings when creating personal template', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
mockUpsertPageTemplateMutation.mockResolvedValue({ mockUpsertPageTemplateMutation.mockResolvedValue({
data: { data: {
@ -679,7 +681,7 @@ describe('useTemplateOperations', () => {
}); });
it('should not update user settings when updating existing personal template', async () => { it('should not update user settings when updating existing personal template', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
mockUpsertPageTemplateMutation.mockResolvedValue({ mockUpsertPageTemplateMutation.mockResolvedValue({
data: { data: {
@ -697,7 +699,7 @@ describe('useTemplateOperations', () => {
}); });
it('should handle template with empty rows', async () => { it('should handle template with empty rows', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const templateWithEmptyRows: PageTemplateFragment = { const templateWithEmptyRows: PageTemplateFragment = {
urn: 'urn:li:pageTemplate:empty', urn: 'urn:li:pageTemplate:empty',
@ -742,7 +744,7 @@ describe('useTemplateOperations', () => {
}); });
it('should handle mutation error', async () => { it('should handle mutation error', async () => {
const { result } = renderHook(() => useTemplateOperations()); const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
const error = new Error('Mutation failed'); const error = new Error('Mutation failed');
mockUpsertPageTemplateMutation.mockRejectedValue(error); mockUpsertPageTemplateMutation.mockRejectedValue(error);
@ -750,4 +752,61 @@ describe('useTemplateOperations', () => {
await expect(result.current.upsertTemplate(mockTemplate, true, null)).rejects.toThrow('Mutation failed'); await expect(result.current.upsertTemplate(mockTemplate, true, null)).rejects.toThrow('Mutation failed');
}); });
}); });
describe('resetTemplateToDefault', () => {
it('should call setPersonalTemplate with null', () => {
const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
act(() => {
result.current.resetTemplateToDefault();
});
expect(setPersonalTemplate).toHaveBeenCalledWith(null);
});
it('should call updateUserHomePageSettingsMutation with pageTemplate: null', () => {
const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
act(() => {
result.current.resetTemplateToDefault();
});
expect(mockUpdateUserHomePageSettings).toHaveBeenCalledWith({
variables: {
input: {
removePageTemplate: true,
},
},
});
});
it('should call setPersonalTemplate and mutation exactly once on multiple calls', () => {
const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
act(() => {
result.current.resetTemplateToDefault();
result.current.resetTemplateToDefault();
});
expect(setPersonalTemplate).toHaveBeenCalledTimes(2);
expect(mockUpdateUserHomePageSettings).toHaveBeenCalledTimes(2);
});
it('should handle async mutation call correctly', async () => {
const { result } = renderHook(() => useTemplateOperations(setPersonalTemplate));
await act(async () => {
await result.current.resetTemplateToDefault();
});
expect(setPersonalTemplate).toHaveBeenCalledWith(null);
expect(mockUpdateUserHomePageSettings).toHaveBeenCalledWith({
variables: {
input: {
removePageTemplate: true,
},
},
});
});
});
}); });

View File

@ -43,7 +43,7 @@ const isValidRemovalPosition = (template: PageTemplateFragment | null, position:
return rowIndex !== undefined && rowIndex >= 0 && rowIndex < rows.length; return rowIndex !== undefined && rowIndex >= 0 && rowIndex < rows.length;
}; };
export function useTemplateOperations() { export function useTemplateOperations(setPersonalTemplate: (template: PageTemplateFragment | null) => void) {
const [upsertPageTemplateMutation] = useUpsertPageTemplateMutation(); const [upsertPageTemplateMutation] = useUpsertPageTemplateMutation();
const [updateUserHomePageSettings] = useUpdateUserHomePageSettingsMutation(); const [updateUserHomePageSettings] = useUpdateUserHomePageSettingsMutation();
@ -194,9 +194,21 @@ export function useTemplateOperations() {
[upsertPageTemplateMutation, updateUserHomePageSettings], [upsertPageTemplateMutation, updateUserHomePageSettings],
); );
const resetTemplateToDefault = () => {
setPersonalTemplate(null);
updateUserHomePageSettings({
variables: {
input: {
removePageTemplate: true,
},
},
});
};
return { return {
updateTemplateWithModule, updateTemplateWithModule,
removeModuleFromTemplate, removeModuleFromTemplate,
upsertTemplate, upsertTemplate,
resetTemplateToDefault,
}; };
} }

View File

@ -60,4 +60,5 @@ export type PageTemplateContextState = {
moduleModalState: ModuleModalState; moduleModalState: ModuleModalState;
removeModule: (input: RemoveModuleInput) => void; removeModule: (input: RemoveModuleInput) => void;
moveModule: (input: MoveModuleInput) => void; moveModule: (input: MoveModuleInput) => void;
resetTemplateToDefault: () => void;
}; };

View File

@ -0,0 +1,116 @@
import { Button, Dropdown, colors } from '@components';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import analytics, { EventType } from '@app/analytics';
import { useUserContext } from '@app/context/useUserContext';
import { usePageTemplateContext } from '@app/homeV3/context/PageTemplateContext';
import { ConfirmationModal } from '@app/sharedV2/modals/ConfirmationModal';
const ButtonWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
background-color: ${colors.white};
height: 40px;
width: 40px;
position: fixed;
right: 32px;
bottom: 32px;
border-radius: 200px;
box-shadow: 0px 4px 12px 0px rgba(9, 1, 61, 0.12);
`;
const DropdownContainer = styled.div`
border-radius: 12px;
box-shadow: 0px 4px 12px 0px rgba(9, 1, 61, 0.12);
background-color: white;
overflow: hidden; // Cleanly rounds edges
.ant-dropdown-menu-item {
padding: 8px 16px;
}
`;
export default function EditHomePageSettingsButton() {
const user = useUserContext();
const canEditDefaultTemplate = user.platformPrivileges?.manageHomePageTemplates;
const { setIsEditingGlobalTemplate, isEditingGlobalTemplate, resetTemplateToDefault, personalTemplate } =
usePageTemplateContext();
const isOnPersonalTemplate = !!personalTemplate;
const [showConfirmResetModal, setShowConfirmResetModal] = useState(false);
const startGlobalTemplateEdit = useCallback(() => {
setIsEditingGlobalTemplate(true);
analytics.event({
type: EventType.HomePageTemplateGlobalTemplateEditingStart,
});
}, [setIsEditingGlobalTemplate]);
const handleResetToDefault = useCallback(() => {
resetTemplateToDefault();
setShowConfirmResetModal(false);
analytics.event({
type: EventType.HomePageTemplateResetToGlobalTemplate,
});
}, [resetTemplateToDefault]);
if (isEditingGlobalTemplate || (!canEditDefaultTemplate && !isOnPersonalTemplate)) return null;
const menu = {
items: [
...(canEditDefaultTemplate
? [
{
label: 'Edit Organization Default',
key: 'edit-organization-default',
style: {
color: colors.gray[600],
fontSize: '14px',
},
onClick: startGlobalTemplateEdit,
},
]
: []),
...(isOnPersonalTemplate
? [
{
label: 'Reset to Organization Default',
key: 'reset-to-organization-default',
style: {
color: colors.red[1000],
fontSize: '14px',
},
onClick: () => setShowConfirmResetModal(true),
},
]
: []),
],
};
return (
<>
<ButtonWrapper>
<Dropdown
menu={menu}
trigger={['click']}
dropdownRender={(menuNode) => <DropdownContainer>{menuNode}</DropdownContainer>}
>
<Button icon={{ icon: 'Gear', color: 'gray', source: 'phosphor', size: '4xl' }} variant="text" />
</Dropdown>
</ButtonWrapper>
<ConfirmationModal
isOpen={!!showConfirmResetModal}
handleConfirm={handleResetToDefault}
handleClose={() => setShowConfirmResetModal(false)}
modalTitle="Confirm reset to default template"
modalText="Are you sure you want to reset your homepage to the organization's default template? You will lose all your personal modules."
closeButtonText="Cancel"
confirmButtonText="Confirm"
/>
</>
);
}

View File

@ -54,7 +54,7 @@ export const ConfirmationModal = ({
centered centered
footer={ footer={
<ButtonsContainer> <ButtonsContainer>
<Button variant="text" onClick={handleClose} data-testid="modal-cancel-button"> <Button variant="text" color="gray" onClick={handleClose} data-testid="modal-cancel-button">
{closeButtonText || 'Cancel'} {closeButtonText || 'Cancel'}
</Button> </Button>
<Button variant="filled" onClick={handleConfirm} data-testid="modal-confirm-button"> <Button variant="filled" onClick={handleConfirm} data-testid="modal-confirm-button">

View File

@ -69,6 +69,7 @@ query getMe {
viewStructuredPropertiesPage viewStructuredPropertiesPage
manageApplications manageApplications
manageFeatures manageFeatures
manageHomePageTemplates
} }
} }
} }