Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions admin-ui/app/components/GluuDropdown/GluuDropdown.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ const getPositionStyles = (position: DropdownPosition) => {
marginLeft: SHARED_DROPDOWN_STYLES.margin,
...baseTransform,
}
case 'bottom-end':
return {
top: '100%',
marginTop: SHARED_DROPDOWN_STYLES.margin,
right: 0,
}
default:
return {}
}
Expand All @@ -54,6 +60,13 @@ const getArrowStyles = (position: DropdownPosition) => {
left: '50%',
transform: 'translateX(-50%)',
}
case 'bottom-end':
return {
top: 'calc(100% - 2px)',
left: '50%',
transform: 'translateX(-50%)',
zIndex: SHARED_DROPDOWN_STYLES.menuZIndex + 1,
}
case 'left':
return {
right: '-15px',
Expand All @@ -76,7 +89,8 @@ export const useStyles = makeStyles<{
position: DropdownPosition
dropdownBg: string
centerText?: boolean
}>()((_theme, { isDark, position, dropdownBg, centerText }) => ({
optionPadding?: string
}>()((_theme, { isDark, position, dropdownBg, centerText, optionPadding }) => ({
dropdownWrapper: {
position: 'relative',
display: 'inline-block',
Expand All @@ -93,7 +107,12 @@ export const useStyles = makeStyles<{
maxHeight: SHARED_DROPDOWN_STYLES.maxHeight,
overflow: 'visible',
...getPositionStyles(position),
marginTop: position === 'bottom' ? '13px' : position === 'top' ? undefined : '4px',
marginTop:
position === 'bottom' || position === 'bottom-end'
? '13px'
: position === 'top'
? undefined
: '4px',
},
dropdownMenuContent: {
padding: SHARED_DROPDOWN_STYLES.padding,
Expand Down Expand Up @@ -140,6 +159,7 @@ export const useStyles = makeStyles<{
...createBaseOptionStyles({
isDark,
...(centerText && { optionPadding: '12px 12px' }),
...(optionPadding && { optionPadding }),
}),
'&.single-option': {
justifyContent: 'center',
Expand Down
13 changes: 10 additions & 3 deletions admin-ui/app/components/GluuDropdown/GluuDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const GluuDropdown = <T extends DropdownValue = DropdownValue>({
renderOption,
renderTrigger,
centerText = false,
optionPadding,
}: GluuDropdownProps<T>): React.ReactElement => {
const [internalState, setInternalState] = useState<DropdownState>({
isOpen: false,
Expand All @@ -66,9 +67,10 @@ export const GluuDropdown = <T extends DropdownValue = DropdownValue>({
const dropdownBg = useMemo(() => {
return isDark ? customColors.darkDropdownBg : customColors.white
}, [isDark])
const { classes } = useStyles({ isDark, position, dropdownBg, centerText })
const { classes } = useStyles({ isDark, position, dropdownBg, centerText, optionPadding })

const isOpen = controlled ? (controlledIsOpen ?? false) : internalState.isOpen
const arrowAnchoredToTrigger = position === 'bottom-end'
const searchQuery = internalState.searchQuery

const setIsOpen = useCallback(
Expand Down Expand Up @@ -152,7 +154,7 @@ export const GluuDropdown = <T extends DropdownValue = DropdownValue>({
option.onClick?.(option.value, option)
onSelect?.(option.value, option)

if (closeOnSelect) {
if (closeOnSelect && !option.keepOpen) {
setIsOpen(false)
setInternalState((prev) => ({ ...prev, searchQuery: '' }))
}
Expand Down Expand Up @@ -269,7 +271,7 @@ export const GluuDropdown = <T extends DropdownValue = DropdownValue>({
role="listbox"
id={listboxId}
>
{showArrow && (
{showArrow && !arrowAnchoredToTrigger && (
<div className={classes.arrow}>
<ArrowIcon />
</div>
Expand All @@ -293,6 +295,11 @@ export const GluuDropdown = <T extends DropdownValue = DropdownValue>({
</div>
</Box>
)}
{isOpen && showArrow && arrowAnchoredToTrigger && (
<div className={classes.arrow}>
<ArrowIcon />
</div>
)}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type React from 'react'

export type DropdownPosition = 'top' | 'bottom' | 'left' | 'right'
export type DropdownPosition = 'top' | 'bottom' | 'bottom-end' | 'left' | 'right'

export type DropdownValue = string | number | boolean

Expand All @@ -13,6 +13,7 @@ export type GluuDropdownOption<T extends DropdownValue = DropdownValue> = {
icon?: React.ReactNode
metadata?: Record<string, string | number | boolean | null | undefined>
searchValue?: string
keepOpen?: boolean
}

export type GluuDropdownProps<T extends DropdownValue = DropdownValue> = {
Expand Down Expand Up @@ -43,6 +44,7 @@ export type GluuDropdownProps<T extends DropdownValue = DropdownValue> = {
selectedOption?: GluuDropdownOption<T> | GluuDropdownOption<T>[],
) => React.ReactNode
centerText?: boolean
optionPadding?: string
}

export type DropdownState = {
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@
"platform": "Platform",
"authType": "Auth Type",
"showCedarLogs?": "Cedarling Log enabled?",
"cedarlingLogs?": "Cedarling logs?",
"reloginToViewCedarlingChanges": "Please Re-login to view the cedarling changes.",
"allAvailableHintsSelected": "All available hint options are selected",
"noMatchingOptions": "No matching options",
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@
"platform": "Plataforma",
"authType": "Tipo de Autenticaci贸n",
"showCedarLogs?": "驴Registro de Cedarling habilitado?",
"cedarlingLogs?": "驴Registros de Cedarling?",
"reloginToViewCedarlingChanges": "Por favor, vuelve a iniciar sesi贸n para ver los cambios de Cedarling.",
"allAvailableHintsSelected": "Todas las opciones de sugerencia disponibles est谩n seleccionadas",
"noMatchingOptions": "No hay opciones coincidentes",
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@
"platform": "Plateforme",
"authType": "Type d'authentification",
"showCedarLogs?": "Cedarling Log est-il activ茅?",
"cedarlingLogs?": "Journaux Cedarling ?",
"reloginToViewCedarlingChanges": "Veuillez vous reconnecter pour voir les modifications de cedarling.",
"allAvailableHintsSelected": "Toutes les options d'indice disponibles sont s茅lectionn茅es.",
"noMatchingOptions": "Aucune option correspondante",
Expand Down
1 change: 1 addition & 0 deletions admin-ui/app/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@
"platform": "Plataforma",
"authType": "Tipo de autentica莽茫o",
"showCedarLogs?": "O Cedarling Log est谩 habilitado?",
"cedarlingLogs?": "Logs do Cedarling?",
"reloginToViewCedarlingChanges": "Por favor, fa莽a login novamente para ver as altera莽玫es do cedarling.",
"allAvailableHintsSelected": "Todas as op莽玫es de dica dispon铆veis est茫o selecionadas",
"noMatchingOptions": "Nenhuma op莽茫o correspondente",
Expand Down
2 changes: 1 addition & 1 deletion admin-ui/app/routes/Apps/Gluu/GluuNavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ const GluuNavBar = () => {
</Box>
</Box>
)}
position="bottom"
position="bottom-end"
/>
)}
</Box>
Expand Down
5 changes: 3 additions & 2 deletions admin-ui/app/routes/Apps/Gluu/GluuToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from 'react'
import type { ChangeEvent } from 'react'
import clsx from 'clsx'
import customColors from '@/customColors'
import type { JsonValue } from './types/common'
import type { GluuToggleProps } from './types/GluuToggle.types'
import { useStyles } from './styles/GluuToggle.style'
Expand Down Expand Up @@ -39,7 +40,7 @@ const GluuToggle = <T = Record<string, JsonValue>,>({
<svg width="14" height="11" viewBox="0 0 14 11">
<path
d="M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0"
fill="#fff"
fill={customColors.white}
fillRule="evenodd"
/>
</svg>
Expand All @@ -48,7 +49,7 @@ const GluuToggle = <T = Record<string, JsonValue>,>({
<svg width="10" height="10" viewBox="0 0 10 10">
<path
d="M9.9 2.12L7.78 0 4.95 2.828 2.12 0 0 2.12l2.83 2.83L0 7.776 2.123 9.9 4.95 7.07 7.78 9.9 9.9 7.776 7.072 4.95 9.9 2.12"
fill="#fff"
fill={customColors.white}
fillRule="evenodd"
/>
</svg>
Expand Down
4 changes: 4 additions & 0 deletions admin-ui/app/routes/Apps/Gluu/__tests__/GluuNavBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const createTestStore = (userinfo: UserInfo | null): Store =>
}),
})

jest.mock('@/utils/hooks/useCedarlingLogToggle', () => ({
useCedarlingLogToggle: () => ({ enabled: false, toggle: jest.fn(), isSaving: false }),
}))

const renderNavBar = (userinfo: UserInfo | null) => {
const store = createTestStore(userinfo)
const Wrapper = ({ children }: { children: ReactNode }) => (
Expand Down
34 changes: 33 additions & 1 deletion admin-ui/app/routes/components/Dropdowns/DropdownProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ import { useAppNavigation, ROUTES } from '@/helpers/navigation'
import { auditLogoutLogs } from 'Redux/features/sessionSlice'
import { MANUAL_LOGOUT } from '@/audit/messages'
import { GluuDropdown, type GluuDropdownOption } from 'Components'
import Box from '@mui/material/Box'
import Switch from '@mui/material/Switch'
import { useCedarlingLogToggle } from '@/utils/hooks/useCedarlingLogToggle'
import type { DropdownProfileProps } from './types'

const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: DropdownProfileProps) => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const { navigateToRoute } = useAppNavigation()
const { enabled: cedarLogsEnabled, toggle: toggleCedarLogs } = useCedarlingLogToggle()

const handleLogout = useCallback(() => {
dispatch(auditLogoutLogs({ message: MANUAL_LOGOUT }))
Expand All @@ -25,6 +29,33 @@ const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: Dropdo
navigateToRoute(ROUTES.PROFILE)
},
},
{
value: 'cedarLogs',
label: (
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
flex: 1,
gap: 2,
whiteSpace: 'nowrap',
}}
>
{t('fields.cedarlingLogs?')}
<Switch
size="small"
checked={cedarLogsEnabled}
slotProps={{ input: { 'aria-label': t('fields.cedarlingLogs?') } }}
/>
</Box>
),
searchValue: t('fields.cedarlingLogs?'),
keepOpen: true,
onClick: () => {
toggleCedarLogs()
},
},
{
value: 'logout',
label: t('menus.signout'),
Expand All @@ -33,7 +64,7 @@ const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: Dropdo
},
},
],
[t, navigateToRoute, handleLogout],
[t, navigateToRoute, handleLogout, cedarLogsEnabled, toggleCedarLogs],
)

return (
Expand All @@ -43,6 +74,7 @@ const DropdownProfile = ({ trigger, renderTrigger, position = 'bottom' }: Dropdo
options={options}
position={position}
minWidth={182}
optionPadding="12px"
showArrow={true}
/>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { THEME_LIGHT, THEME_DARK } from '@/context/theme/constants'
import { useThemePersistence } from '@/hooks/useThemePersistence'
import { useLangPersistence } from '@/hooks/useLangPersistence'
import { LANG_CODES, DEFAULT_LANG } from '@/constants'
import Switch from '@mui/material/Switch'
import { useCedarlingLogToggle } from '@/utils/hooks/useCedarlingLogToggle'
import { useStyles } from './styles/MobileProfileDropdown.style'
import type { MobileProfileDropdownProps } from './types'

Expand Down Expand Up @@ -89,6 +91,8 @@ const MobileProfileDropdown = ({ userInfo, renderTrigger }: MobileProfileDropdow

const onChangeTheme = useThemePersistence(userInfo)

const { enabled: cedarLogsEnabled, toggle: toggleCedarLogs } = useCedarlingLogToggle()

const handleProfile = useCallback(() => {
setIsOpen(false)
navigateToRoute(ROUTES.PROFILE)
Expand Down Expand Up @@ -219,6 +223,20 @@ const MobileProfileDropdown = ({ userInfo, renderTrigger }: MobileProfileDropdow
/>
</div>

<hr className={classes.divider} />

<div className={classes.row}>
<GluuText variant="span" className={classes.rowLabel}>
{t('fields.cedarlingLogs?')}
</GluuText>
<Switch
size="small"
checked={cedarLogsEnabled}
onChange={toggleCedarLogs}
slotProps={{ input: { 'aria-label': t('fields.cedarlingLogs?') } }}
/>
</div>

<button type="button" className={classes.signOut} onClick={handleLogout}>
<GluuText variant="span" className={classes.signOutText}>
{t('menus.signout')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import { DropdownProfile } from '../DropdownProfile'
import sessionReducer, { auditLogoutLogs } from 'Redux/features/sessionSlice'
import { ROUTES } from '@/helpers/navigation'

const mockToggleCedarLogs = jest.fn()

jest.mock('@/utils/hooks/useCedarlingLogToggle', () => ({
useCedarlingLogToggle: () => ({
enabled: false,
toggle: mockToggleCedarLogs,
isSaving: false,
}),
}))
Comment thread
faisalsiddique4400 marked this conversation as resolved.

const mockNavigateToRoute = jest.fn()

jest.mock('@/helpers/navigation', () => ({
Expand Down Expand Up @@ -40,6 +50,7 @@ const openMenu = () => fireEvent.click(screen.getByText('Account'))
describe('DropdownProfile', () => {
beforeEach(() => {
mockNavigateToRoute.mockClear()
mockToggleCedarLogs.mockClear()
})

it('renders the trigger', () => {
Expand All @@ -61,6 +72,27 @@ describe('DropdownProfile', () => {
expect(mockNavigateToRoute).toHaveBeenCalledWith(ROUTES.PROFILE)
})

it('toggles cedarling logs once when the option is activated', () => {
renderProfile()
openMenu()
fireEvent.click(screen.getByRole('switch', { name: i18n.t('fields.cedarlingLogs?') }))
expect(mockToggleCedarLogs).toHaveBeenCalledTimes(1)
})

it('keeps the menu open after cedarling logs is toggled', () => {
renderProfile()
openMenu()
fireEvent.click(screen.getByRole('switch', { name: i18n.t('fields.cedarlingLogs?') }))
expect(screen.getByText(i18n.t('menus.signout'))).toBeInTheDocument()
})

it('closes the menu after sign out is clicked', () => {
renderProfile()
openMenu()
fireEvent.click(screen.getByText(i18n.t('menus.signout')))
expect(screen.queryByText(i18n.t('menus.my_profile'))).not.toBeInTheDocument()
})

it('dispatches a logout audit when sign out is clicked', () => {
const { dispatchSpy } = renderProfile()
openMenu()
Expand Down
Loading
Loading