Skip to content

Commit

Permalink
Merge pull request #11034 from bnussman-akamai/fix/timezone-select
Browse files Browse the repository at this point in the history
fix: [M3-8685] - Users unable to update timezone
  • Loading branch information
coliu-akamai authored Oct 2, 2024
2 parents 3f8aa77 + d5456e3 commit ca5d0c9
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 103 deletions.
1 change: 1 addition & 0 deletions packages/manager/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

- DBaaS Landing Page V2 platform error for New Beta Users ([#11024](https://github.com/linode/manager/pull/11024))
- Add Volume button on Linodes Storage tab causing page crash ([#11032](https://github.com/linode/manager/pull/11032))
- Users unable to update to update profile timezone ([#11034](https://github.com/linode/manager/pull/11034))

## [2024-09-30] - v1.129.0

Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/assets/timezones/timezones.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// eslint-disable-next-line max-len
// [...$0.children].map(el => ({ label: (el.getAttribute('aria-label')|| '').replace(/\(.*?\)(.+)/, '$1').trim(), name: el.getAttribute('data-value'), offset: +(el.getAttribute('aria-label')|| '').replace(/\(.*?(-?[0-9]{2}):([0-9]{2})\).*/, (all, one, two) => +one + (two / 60) * (one > 0 ? 1 : -1)) }))

export default [
export const timezones = [
{
label: 'Niue Time',
name: 'Pacific/Niue',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { HttpResponse, http, server } from 'src/mocks/testServer';
import { queryClientFactory } from 'src/queries/base';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { TimezoneForm, formatOffset } from './TimezoneForm';
import { TimezoneForm, getOptionLabel } from './TimezoneForm';

const queryClient = queryClientFactory();

Expand Down Expand Up @@ -75,7 +75,7 @@ describe('formatOffset', () => {
];

testMap.forEach(({ expectedOffset, timezone }) =>
expect(formatOffset(timezone)).toBe(
expect(getOptionLabel(timezone)).toBe(
`(GMT ${expectedOffset}) ${timezone.label}`
)
);
Expand Down
152 changes: 52 additions & 100 deletions packages/manager/src/features/Profile/DisplaySettings/TimezoneForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,160 +3,112 @@ import { DateTime } from 'luxon';
import { useSnackbar } from 'notistack';
import * as React from 'react';

import timezones from 'src/assets/timezones/timezones';
import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { timezones } from 'src/assets/timezones/timezones';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Button } from 'src/components/Button/Button';
import { CircleProgress } from 'src/components/CircleProgress';
import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { Notice } from 'src/components/Notice/Notice';
import { useMutateProfile, useProfile } from 'src/queries/profile/profile';

interface Props {
loggedInAsCustomer: boolean;
}

interface Timezone {
label: string;
name: string;
offset: number;
}

export interface TimezoneOption<T = string, L = string> {
label: L;
value: T;
}
type Timezone = typeof timezones[number];

export const formatOffset = ({ label, offset }: Timezone) => {
export const getOptionLabel = ({ label, offset }: Timezone) => {
const minutes = (Math.abs(offset) % 60).toLocaleString(undefined, {
minimumIntegerDigits: 2,
useGrouping: false,
});
const hours = Math.floor(Math.abs(offset) / 60);
const isPositive = Math.abs(offset) === offset ? '+' : '-';

return `\(GMT ${isPositive}${hours}:${minutes}\) ${label}`;
return `(GMT ${isPositive}${hours}:${minutes}) ${label}`;
};

const renderTimezonesList = (): TimezoneOption<string>[] => {
const getTimezoneOptions = () => {
return timezones
.map((tz) => ({ ...tz, offset: DateTime.now().setZone(tz.name).offset }))
.sort((a, b) => a.offset - b.offset)
.map((tz: Timezone) => {
const label = formatOffset(tz);
return { label, value: tz.name };
});
.map((tz) => {
// We use Luxon to get the offset because it correctly factors in Daylight Savings Time (see https://github.com/linode/manager/pull/8526)
const offset = DateTime.now().setZone(tz.name).offset;
const label = getOptionLabel({ ...tz, offset });
return { label, offset, value: tz.name };
})
.sort((a, b) => a.offset - b.offset);
};

const timezoneList = renderTimezonesList();
const timezoneOptions = getTimezoneOptions();

export const TimezoneForm = (props: Props) => {
const { loggedInAsCustomer } = props;
const { enqueueSnackbar } = useSnackbar();
const { data: profile } = useProfile();
const [timezoneValue, setTimezoneValue] = React.useState<
TimezoneOption<string> | string
>('');
const { error, isPending, mutateAsync: updateProfile } = useMutateProfile();
const timezone = profile?.timezone ?? '';

const handleTimezoneChange = (timezone: TimezoneOption<string>) => {
setTimezoneValue(timezone);
};
const [timezoneValue, setTimezoneValue] = React.useState(profile?.timezone);

const onSubmit = () => {
if (timezoneValue === '') {
return;
if (!timezoneValue) {
enqueueSnackbar('Please select a valid timezone.', { variant: 'error' });
}

updateProfile({ timezone: String(timezoneValue) }).then(() => {
updateProfile({ timezone: timezoneValue }).then(() => {
enqueueSnackbar('Successfully updated timezone', { variant: 'success' });
});
};

const defaultTimezone = timezoneList.find((eachZone) => {
return eachZone.value === timezone;
});

const disabled =
timezoneValue === '' || defaultTimezone?.value === timezoneValue;
const disabled = !timezoneValue || profile?.timezone === timezoneValue;

if (!profile) {
return <CircleProgress />;
}

return (
<>
{loggedInAsCustomer ? (
<StyledLoggedInAsCustomerNotice data-testid="admin-notice">
<Typography variant="h2">
While you are logged in as a customer, all times, dates, and graphs
will be displayed in your browser&rsquo;s timezone ({timezone}).
</Typography>
</StyledLoggedInAsCustomerNotice>
) : null}
<StyledRootContainer>
<Stack>
<Autocomplete
slotProps={{
popper: {
sx: {
maxHeight: '285px',
overflow: 'hidden',
},
},
}}
value={timezoneList.find(
(option) => option.value === timezoneValue
)}
autoHighlight
data-qa-tz-select
defaultValue={defaultTimezone}
disableClearable
errorText={error?.[0].reason}
label="Timezone"
onChange={(_, option) => handleTimezoneChange(option)}
options={timezoneList}
placeholder="Choose a Timezone"
sx={{ width: '416px' }}
/>
</Stack>
<ActionsPanel
primaryButtonProps={{
disabled,
label: 'Update Timezone',
loading: isPending,
onClick: onSubmit,
sx: {
margin: '0',
minWidth: 180,
},
}}
sx={{
padding: 0,
}}
{loggedInAsCustomer && (
<Notice dataTestId="admin-notice" variant="error">
While you are logged in as a customer, all times, dates, and graphs
will be displayed in the user&rsquo;s timezone ({profile.timezone}).
</Notice>
)}
<TimezoneFormContainer>
<Autocomplete
value={timezoneOptions.find(
(option) => option.value === timezoneValue
)}
autoHighlight
disableClearable
errorText={error?.[0].reason}
fullWidth
label="Timezone"
onChange={(e, option) => setTimezoneValue(option.value)}
options={timezoneOptions}
placeholder="Choose a Timezone"
/>
</StyledRootContainer>
<Button
buttonType="primary"
disabled={disabled}
loading={isPending}
onClick={onSubmit}
sx={{ minWidth: 180 }}
>
Update Timezone
</Button>
</TimezoneFormContainer>
</>
);
};

const StyledRootContainer = styled('div', {
label: 'StyledRootContainer',
const TimezoneFormContainer = styled('div', {
label: 'TimezoneFormContainer',
})(({ theme }) => ({
alignItems: 'flex-end',
display: 'flex',
justifyContent: 'space-between',
[theme.breakpoints.down('md')]: {
alignItems: 'flex-start',
flexDirection: 'column',
gap: theme.spacing(),
},
}));

const StyledLoggedInAsCustomerNotice = styled('div', {
label: 'StyledLoggedInAsCustomerNotice',
})(({ theme }) => ({
backgroundColor: theme.color.red,
marginBottom: 8,
padding: 16,
textAlign: 'center',
}));

0 comments on commit ca5d0c9

Please sign in to comment.