mirror of
https://github.com/strapi/strapi.git
synced 2025-11-02 02:44:55 +00:00
future: add reset guided tour to profile (#23984)
Co-authored-by: Simone <startae14@gmail.com>
This commit is contained in:
parent
68865f630a
commit
0fe043296d
@ -3,7 +3,6 @@ import * as React from 'react';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { GetGuidedTourMeta } from '../../../../shared/contracts/admin';
|
||||
import { useTracking } from '../../features/Tracking';
|
||||
import { usePersistentState } from '../../hooks/usePersistentState';
|
||||
import { createContext } from '../Context';
|
||||
|
||||
@ -35,6 +34,9 @@ type Action =
|
||||
}
|
||||
| {
|
||||
type: 'skip_all_tours';
|
||||
}
|
||||
| {
|
||||
type: 'reset_all_tours';
|
||||
};
|
||||
|
||||
type Tour = Record<ValidTourName, { currentStep: number; length: number; isCompleted: boolean }>;
|
||||
@ -49,6 +51,17 @@ const [GuidedTourProviderImpl, unstableUseGuidedTour] = createContext<{
|
||||
dispatch: React.Dispatch<Action>;
|
||||
}>('UnstableGuidedTour');
|
||||
|
||||
const initialTourState = Object.keys(guidedTours).reduce((acc, tourName) => {
|
||||
const tourLength = Object.keys(guidedTours[tourName as ValidTourName]).length;
|
||||
acc[tourName as ValidTourName] = {
|
||||
currentStep: 0,
|
||||
length: tourLength,
|
||||
isCompleted: false,
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {} as Tour);
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
return produce(state, (draft) => {
|
||||
if (action.type === 'next_step') {
|
||||
@ -68,6 +81,12 @@ function reducer(state: State, action: Action): State {
|
||||
if (action.type === 'skip_all_tours') {
|
||||
draft.enabled = false;
|
||||
}
|
||||
|
||||
if (action.type === 'reset_all_tours') {
|
||||
draft.enabled = true;
|
||||
draft.tours = initialTourState;
|
||||
draft.completedActions = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -79,17 +98,6 @@ const UnstableGuidedTourContext = ({
|
||||
children: React.ReactNode;
|
||||
enabled?: boolean;
|
||||
}) => {
|
||||
const initialTourState = Object.keys(guidedTours).reduce((acc, tourName) => {
|
||||
const tourLength = Object.keys(guidedTours[tourName as ValidTourName]).length;
|
||||
acc[tourName as ValidTourName] = {
|
||||
currentStep: 0,
|
||||
length: tourLength,
|
||||
isCompleted: false,
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {} as Tour);
|
||||
|
||||
const [tours, setTours] = usePersistentState<State>(STORAGE_KEY, {
|
||||
tours: initialTourState,
|
||||
enabled,
|
||||
|
||||
@ -140,13 +140,15 @@ const WaveIcon = () => {
|
||||
|
||||
export const UnstableGuidedTourOverview = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { trackUsage } = useTracking();
|
||||
|
||||
const tours = unstableUseGuidedTour('Overview', (s) => s.state.tours);
|
||||
const dispatch = unstableUseGuidedTour('Overview', (s) => s.dispatch);
|
||||
const enabled = unstableUseGuidedTour('Overview', (s) => s.state.enabled);
|
||||
const completedActions = unstableUseGuidedTour('Overview', (s) => s.state.completedActions);
|
||||
const { data: guidedTourMeta } = useGetGuidedTourMetaQuery();
|
||||
const tourNames = Object.keys(tours) as ValidTourName[];
|
||||
const { trackUsage } = useTracking();
|
||||
|
||||
const tourNames = Object.keys(tours) as ValidTourName[];
|
||||
const completedTours = tourNames.filter((tourName) => tours[tourName].isCompleted);
|
||||
const completionPercentage =
|
||||
tourNames.length > 0 ? Math.round((completedTours.length / tourNames.length) * 100) : 0;
|
||||
@ -160,7 +162,7 @@ export const UnstableGuidedTourOverview = () => {
|
||||
dispatch({ type: 'skip_all_tours' });
|
||||
};
|
||||
|
||||
const handleClickOnLink = (tourName: ValidTourName) => {
|
||||
const handleClickStrapiCloud = (tourName: ValidTourName) => {
|
||||
trackUsage('didCompleteGuidedTour', { name: tourName });
|
||||
dispatch({ type: 'skip_tour', payload: tourName });
|
||||
};
|
||||
@ -186,7 +188,7 @@ export const UnstableGuidedTourOverview = () => {
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
alignItems="start"
|
||||
alignItems="flex-start"
|
||||
width="100%"
|
||||
paddingTop={5}
|
||||
paddingBottom={8}
|
||||
@ -225,6 +227,9 @@ export const UnstableGuidedTourOverview = () => {
|
||||
{TASK_CONTENT.map((task) => {
|
||||
const tourName = task.tourName as ValidTourName;
|
||||
const tour = tours[tourName];
|
||||
const isLinkDisabled =
|
||||
tourName !== 'contentTypeBuilder' &&
|
||||
!completedActions.includes('didCreateContentTypeSchema');
|
||||
|
||||
return (
|
||||
<TourTaskContainer key={tourName} alignItems="center" justifyContent="space-between">
|
||||
@ -251,14 +256,16 @@ export const UnstableGuidedTourOverview = () => {
|
||||
{task.isExternal ? (
|
||||
<Link
|
||||
isExternal
|
||||
disabled={isLinkDisabled}
|
||||
href={task.link.to}
|
||||
onClick={() => handleClickOnLink(task.tourName as ValidTourName)}
|
||||
onClick={() => handleClickStrapiCloud(task.tourName as ValidTourName)}
|
||||
>
|
||||
{formatMessage(task.link.label)}
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
endIcon={<ChevronRight />}
|
||||
disabled={isLinkDisabled}
|
||||
to={task.link.to}
|
||||
tag={NavLink}
|
||||
onClick={() =>
|
||||
|
||||
@ -717,4 +717,199 @@ describe('GuidedTour | reducer', () => {
|
||||
expect(reducer(initialState, action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset_all_tours', () => {
|
||||
it('should reset when all tours have been completed', () => {
|
||||
const initialState = {
|
||||
tours: {
|
||||
contentTypeBuilder: {
|
||||
currentStep: 5,
|
||||
isCompleted: true,
|
||||
length: 5,
|
||||
},
|
||||
contentManager: {
|
||||
currentStep: 4,
|
||||
isCompleted: true,
|
||||
length: 4,
|
||||
},
|
||||
apiTokens: {
|
||||
currentStep: 4,
|
||||
isCompleted: true,
|
||||
length: 4,
|
||||
},
|
||||
strapiCloud: {
|
||||
currentStep: 0,
|
||||
isCompleted: true,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
completedActions: [
|
||||
'didCreateContentTypeSchema',
|
||||
'didCopyApiToken',
|
||||
'didCreateApiToken',
|
||||
] as ExtendedCompletedActions,
|
||||
};
|
||||
|
||||
const action: Action = {
|
||||
type: 'reset_all_tours',
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
tours: {
|
||||
contentTypeBuilder: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 5,
|
||||
},
|
||||
contentManager: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
apiTokens: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
strapiCloud: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
completedActions: [] as ExtendedCompletedActions,
|
||||
};
|
||||
|
||||
expect(reducer(initialState, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should reset when some tours have been completed', () => {
|
||||
const initialState = {
|
||||
tours: {
|
||||
contentTypeBuilder: {
|
||||
currentStep: 2,
|
||||
isCompleted: false,
|
||||
length: 5,
|
||||
},
|
||||
contentManager: {
|
||||
currentStep: 4,
|
||||
isCompleted: true,
|
||||
length: 4,
|
||||
},
|
||||
apiTokens: {
|
||||
currentStep: 1,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
strapiCloud: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
completedActions: ['didCreateContentTypeSchema'] as ExtendedCompletedActions,
|
||||
};
|
||||
|
||||
const action: Action = {
|
||||
type: 'reset_all_tours',
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
tours: {
|
||||
contentTypeBuilder: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 5,
|
||||
},
|
||||
contentManager: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
apiTokens: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
strapiCloud: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
completedActions: [] as ExtendedCompletedActions,
|
||||
};
|
||||
|
||||
expect(reducer(initialState, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('should reset when tour is disabled', () => {
|
||||
const initialState = {
|
||||
tours: {
|
||||
contentTypeBuilder: {
|
||||
currentStep: 3,
|
||||
isCompleted: false,
|
||||
length: 5,
|
||||
},
|
||||
contentManager: {
|
||||
currentStep: 2,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
apiTokens: {
|
||||
currentStep: 4,
|
||||
isCompleted: true,
|
||||
length: 4,
|
||||
},
|
||||
strapiCloud: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
enabled: false,
|
||||
completedActions: [
|
||||
'didCreateContentTypeSchema',
|
||||
'didCopyApiToken',
|
||||
] as ExtendedCompletedActions,
|
||||
};
|
||||
|
||||
const action: Action = {
|
||||
type: 'reset_all_tours',
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
tours: {
|
||||
contentTypeBuilder: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 5,
|
||||
},
|
||||
contentManager: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
apiTokens: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 4,
|
||||
},
|
||||
strapiCloud: {
|
||||
currentStep: 0,
|
||||
isCompleted: false,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
completedActions: [] as ExtendedCompletedActions,
|
||||
};
|
||||
|
||||
expect(reducer(initialState, action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Box, Button, Flex, useNotifyAT, Grid, Typography } from '@strapi/design-system';
|
||||
import { Box, Button, Flex, useNotifyAT, Grid, Typography, FlexProps } from '@strapi/design-system';
|
||||
import { Check } from '@strapi/icons';
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import { useIntl } from 'react-intl';
|
||||
@ -10,6 +10,7 @@ import { Form, FormHelpers } from '../components/Form';
|
||||
import { InputRenderer } from '../components/FormInputs/Renderer';
|
||||
import { Layouts } from '../components/Layouts/Layout';
|
||||
import { Page } from '../components/PageHelpers';
|
||||
import { unstableUseGuidedTour } from '../components/UnstableGuidedTour/Context';
|
||||
import { useTypedDispatch, useTypedSelector } from '../core/store/hooks';
|
||||
import { useAuth } from '../features/Auth';
|
||||
import { useNotification } from '../features/Notifications';
|
||||
@ -47,6 +48,24 @@ const PROFILE_VALIDTION_SCHEMA = yup.object().shape({
|
||||
* ProfilePage
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const Panel = ({ children, ...flexProps }: FlexProps) => {
|
||||
return (
|
||||
<Box
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Flex direction="column" alignItems="stretch" gap={4} {...flexProps}>
|
||||
{children}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfilePage = () => {
|
||||
const localeNames = useTypedSelector((state) => state.admin_app.language.localeNames);
|
||||
const { formatMessage } = useIntl();
|
||||
@ -196,7 +215,7 @@ const ProfilePage = () => {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Box paddingBottom={10}>
|
||||
<Box paddingBottom={6}>
|
||||
<Layouts.Content>
|
||||
<Flex direction="column" alignItems="stretch" gap={6}>
|
||||
<UserInfoSection />
|
||||
@ -208,6 +227,11 @@ const ProfilePage = () => {
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
<Box paddingBottom={10}>
|
||||
<Layouts.Content>
|
||||
<GuidedTourSection />
|
||||
</Layouts.Content>
|
||||
</Box>
|
||||
</Page.Main>
|
||||
);
|
||||
};
|
||||
@ -220,67 +244,57 @@ const PasswordSection = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Box
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Flex direction="column" alignItems="stretch" gap={4}>
|
||||
<Typography variant="delta" tag="h2">
|
||||
{formatMessage({
|
||||
id: 'global.change-password',
|
||||
defaultMessage: 'Change password',
|
||||
})}
|
||||
</Typography>
|
||||
{[
|
||||
[
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.currentPassword.label',
|
||||
defaultMessage: 'Current Password',
|
||||
}),
|
||||
name: 'currentPassword',
|
||||
size: 6,
|
||||
type: 'password' as const,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
autoComplete: 'new-password',
|
||||
label: formatMessage({
|
||||
id: 'global.password',
|
||||
defaultMessage: 'Password',
|
||||
}),
|
||||
name: 'password',
|
||||
size: 6,
|
||||
type: 'password' as const,
|
||||
},
|
||||
{
|
||||
autoComplete: 'new-password',
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.confirmPassword.label',
|
||||
defaultMessage: 'Confirm Password',
|
||||
}),
|
||||
name: 'confirmPassword',
|
||||
size: 6,
|
||||
type: 'password' as const,
|
||||
},
|
||||
],
|
||||
].map((row, index) => (
|
||||
<Grid.Root key={index} gap={5}>
|
||||
{row.map(({ size, ...field }) => (
|
||||
<Grid.Item key={field.name} col={size} direction="column" alignItems="stretch">
|
||||
<InputRenderer {...field} />
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid.Root>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
<Panel>
|
||||
<Typography variant="delta" tag="h2">
|
||||
{formatMessage({
|
||||
id: 'global.change-password',
|
||||
defaultMessage: 'Change password',
|
||||
})}
|
||||
</Typography>
|
||||
{[
|
||||
[
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.currentPassword.label',
|
||||
defaultMessage: 'Current Password',
|
||||
}),
|
||||
name: 'currentPassword',
|
||||
size: 6,
|
||||
type: 'password' as const,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
autoComplete: 'new-password',
|
||||
label: formatMessage({
|
||||
id: 'global.password',
|
||||
defaultMessage: 'Password',
|
||||
}),
|
||||
name: 'password',
|
||||
size: 6,
|
||||
type: 'password' as const,
|
||||
},
|
||||
{
|
||||
autoComplete: 'new-password',
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.confirmPassword.label',
|
||||
defaultMessage: 'Confirm Password',
|
||||
}),
|
||||
name: 'confirmPassword',
|
||||
size: 6,
|
||||
type: 'password' as const,
|
||||
},
|
||||
],
|
||||
].map((row, index) => (
|
||||
<Grid.Root key={index} gap={5}>
|
||||
{row.map(({ size, ...field }) => (
|
||||
<Grid.Item key={field.name} col={size} direction="column" alignItems="stretch">
|
||||
<InputRenderer {...field} />
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid.Root>
|
||||
))}
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@ -297,121 +311,111 @@ const PreferencesSection = ({ localeNames }: PreferencesSectionProps) => {
|
||||
const themesToDisplay = useTypedSelector((state) => state.admin_app.theme.availableThemes);
|
||||
|
||||
return (
|
||||
<Box
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Flex direction="column" alignItems="stretch" gap={4}>
|
||||
<Flex direction="column" alignItems="stretch" gap={1}>
|
||||
<Typography variant="delta" tag="h2">
|
||||
{formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.title',
|
||||
defaultMessage: 'Experience',
|
||||
})}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{formatMessage(
|
||||
{
|
||||
id: 'Settings.profile.form.section.experience.interfaceLanguageHelp',
|
||||
defaultMessage:
|
||||
'Preference changes will apply only to you. More information is available {here}.',
|
||||
},
|
||||
{
|
||||
here: (
|
||||
<Box
|
||||
tag="a"
|
||||
color="primary600"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.strapi.io/developer-docs/latest/development/admin-customization.html#locales"
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.here',
|
||||
defaultMessage: 'here',
|
||||
})}
|
||||
</Box>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Typography>
|
||||
</Flex>
|
||||
<Grid.Root gap={5}>
|
||||
{[
|
||||
<Panel>
|
||||
<Flex direction="column" alignItems="stretch" gap={1}>
|
||||
<Typography variant="delta" tag="h2">
|
||||
{formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.title',
|
||||
defaultMessage: 'Experience',
|
||||
})}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{formatMessage(
|
||||
{
|
||||
hint: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.interfaceLanguage.hint',
|
||||
defaultMessage: 'This will only display your own interface in the chosen language.',
|
||||
}),
|
||||
label: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.interfaceLanguage',
|
||||
defaultMessage: 'Interface language',
|
||||
}),
|
||||
name: 'preferedLanguage',
|
||||
options: Object.entries(localeNames).map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
})),
|
||||
placeholder: formatMessage({
|
||||
id: 'global.select',
|
||||
defaultMessage: 'Select',
|
||||
}),
|
||||
size: 6,
|
||||
type: 'enumeration' as const,
|
||||
id: 'Settings.profile.form.section.experience.interfaceLanguageHelp',
|
||||
defaultMessage:
|
||||
'Preference changes will apply only to you. More information is available {here}.',
|
||||
},
|
||||
{
|
||||
hint: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.mode.hint',
|
||||
defaultMessage: 'Displays your interface in the chosen mode.',
|
||||
}),
|
||||
label: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.mode.label',
|
||||
defaultMessage: 'Interface mode',
|
||||
}),
|
||||
name: 'currentTheme',
|
||||
options: [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.mode.option-system-label',
|
||||
defaultMessage: 'Use system settings',
|
||||
}),
|
||||
value: 'system',
|
||||
},
|
||||
...themesToDisplay.map((theme) => ({
|
||||
label: formatMessage(
|
||||
{
|
||||
id: 'Settings.profile.form.section.experience.mode.option-label',
|
||||
defaultMessage: '{name} mode',
|
||||
},
|
||||
{
|
||||
name: formatMessage({
|
||||
id: theme,
|
||||
defaultMessage: upperFirst(theme),
|
||||
}),
|
||||
}
|
||||
),
|
||||
value: theme,
|
||||
})),
|
||||
],
|
||||
placeholder: formatMessage({
|
||||
id: 'components.Select.placeholder',
|
||||
defaultMessage: 'Select',
|
||||
}),
|
||||
size: 6,
|
||||
type: 'enumeration' as const,
|
||||
},
|
||||
].map(({ size, ...field }) => (
|
||||
<Grid.Item key={field.name} col={size} direction="column" alignItems="stretch">
|
||||
<InputRenderer {...field} />
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid.Root>
|
||||
here: (
|
||||
<Box
|
||||
tag="a"
|
||||
color="primary600"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.strapi.io/developer-docs/latest/development/admin-customization.html#locales"
|
||||
>
|
||||
{formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.here',
|
||||
defaultMessage: 'here',
|
||||
})}
|
||||
</Box>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Typography>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Grid.Root gap={5}>
|
||||
{[
|
||||
{
|
||||
hint: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.interfaceLanguage.hint',
|
||||
defaultMessage: 'This will only display your own interface in the chosen language.',
|
||||
}),
|
||||
label: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.interfaceLanguage',
|
||||
defaultMessage: 'Interface language',
|
||||
}),
|
||||
name: 'preferedLanguage',
|
||||
options: Object.entries(localeNames).map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
})),
|
||||
placeholder: formatMessage({
|
||||
id: 'global.select',
|
||||
defaultMessage: 'Select',
|
||||
}),
|
||||
size: 6,
|
||||
type: 'enumeration' as const,
|
||||
},
|
||||
{
|
||||
hint: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.mode.hint',
|
||||
defaultMessage: 'Displays your interface in the chosen mode.',
|
||||
}),
|
||||
label: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.mode.label',
|
||||
defaultMessage: 'Interface mode',
|
||||
}),
|
||||
name: 'currentTheme',
|
||||
options: [
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Settings.profile.form.section.experience.mode.option-system-label',
|
||||
defaultMessage: 'Use system settings',
|
||||
}),
|
||||
value: 'system',
|
||||
},
|
||||
...themesToDisplay.map((theme) => ({
|
||||
label: formatMessage(
|
||||
{
|
||||
id: 'Settings.profile.form.section.experience.mode.option-label',
|
||||
defaultMessage: '{name} mode',
|
||||
},
|
||||
{
|
||||
name: formatMessage({
|
||||
id: theme,
|
||||
defaultMessage: upperFirst(theme),
|
||||
}),
|
||||
}
|
||||
),
|
||||
value: theme,
|
||||
})),
|
||||
],
|
||||
placeholder: formatMessage({
|
||||
id: 'components.Select.placeholder',
|
||||
defaultMessage: 'Select',
|
||||
}),
|
||||
size: 6,
|
||||
type: 'enumeration' as const,
|
||||
},
|
||||
].map(({ size, ...field }) => (
|
||||
<Grid.Item key={field.name} col={size} direction="column" alignItems="stretch">
|
||||
<InputRenderer {...field} />
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid.Root>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@ -423,70 +427,106 @@ const UserInfoSection = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Box
|
||||
background="neutral0"
|
||||
hasRadius
|
||||
shadow="filterShadow"
|
||||
paddingTop={6}
|
||||
paddingBottom={6}
|
||||
paddingLeft={7}
|
||||
paddingRight={7}
|
||||
>
|
||||
<Flex direction="column" alignItems="stretch" gap={4}>
|
||||
<Panel>
|
||||
<Typography variant="delta" tag="h2">
|
||||
{formatMessage({
|
||||
id: 'global.profile',
|
||||
defaultMessage: 'Profile',
|
||||
})}
|
||||
</Typography>
|
||||
<Grid.Root gap={5}>
|
||||
{[
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.firstname.label',
|
||||
defaultMessage: 'First name',
|
||||
}),
|
||||
name: 'firstname',
|
||||
required: true,
|
||||
size: 6,
|
||||
type: 'string' as const,
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.lastname.label',
|
||||
defaultMessage: 'Last name',
|
||||
}),
|
||||
name: 'lastname',
|
||||
size: 6,
|
||||
type: 'string' as const,
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.email.label',
|
||||
defaultMessage: 'Email',
|
||||
}),
|
||||
name: 'email',
|
||||
required: true,
|
||||
size: 6,
|
||||
type: 'email' as const,
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.username.label',
|
||||
defaultMessage: 'Username',
|
||||
}),
|
||||
name: 'username',
|
||||
size: 6,
|
||||
type: 'string' as const,
|
||||
},
|
||||
].map(({ size, ...field }) => (
|
||||
<Grid.Item key={field.name} col={size} direction="column" alignItems="stretch">
|
||||
<InputRenderer {...field} />
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid.Root>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* GuidedTourSection
|
||||
* -----------------------------------------------------------------------------------------------*/
|
||||
|
||||
const GuidedTourSection = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { toggleNotification } = useNotification();
|
||||
const dispatch = unstableUseGuidedTour('ProfilePage', (s) => s.dispatch);
|
||||
|
||||
const onClickReset = () => {
|
||||
dispatch({ type: 'reset_all_tours' });
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: formatMessage({
|
||||
id: 'tours.profile.notification.success.reset',
|
||||
defaultMessage: 'Guided tour reset',
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Panel alignItems="start">
|
||||
<Flex direction="column" alignItems="start" gap={1}>
|
||||
<Typography variant="delta" tag="h2">
|
||||
{formatMessage({
|
||||
id: 'global.profile',
|
||||
defaultMessage: 'Profile',
|
||||
id: 'tours.profile.title',
|
||||
defaultMessage: 'Guided tour',
|
||||
})}
|
||||
</Typography>
|
||||
<Typography variant="pi">
|
||||
{formatMessage({
|
||||
id: 'tours.profile.description',
|
||||
defaultMessage: 'You can reset the guided tour at any time.',
|
||||
})}
|
||||
</Typography>
|
||||
<Grid.Root gap={5}>
|
||||
{[
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.firstname.label',
|
||||
defaultMessage: 'First name',
|
||||
}),
|
||||
name: 'firstname',
|
||||
required: true,
|
||||
size: 6,
|
||||
type: 'string' as const,
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.lastname.label',
|
||||
defaultMessage: 'Last name',
|
||||
}),
|
||||
name: 'lastname',
|
||||
size: 6,
|
||||
type: 'string' as const,
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.email.label',
|
||||
defaultMessage: 'Email',
|
||||
}),
|
||||
name: 'email',
|
||||
required: true,
|
||||
size: 6,
|
||||
type: 'email' as const,
|
||||
},
|
||||
{
|
||||
label: formatMessage({
|
||||
id: 'Auth.form.username.label',
|
||||
defaultMessage: 'Username',
|
||||
}),
|
||||
name: 'username',
|
||||
size: 6,
|
||||
type: 'string' as const,
|
||||
},
|
||||
].map(({ size, ...field }) => (
|
||||
<Grid.Item key={field.name} col={size} direction="column" alignItems="stretch">
|
||||
<InputRenderer {...field} />
|
||||
</Grid.Item>
|
||||
))}
|
||||
</Grid.Root>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Button variant="tertiary" onClick={onClickReset}>
|
||||
{formatMessage({
|
||||
id: 'tours.profile.reset',
|
||||
defaultMessage: 'Reset guided tour',
|
||||
})}
|
||||
</Button>
|
||||
</Panel>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -840,5 +840,9 @@
|
||||
"tours.overview.tour.link": "Start",
|
||||
"tours.overview.tour.done": "Done",
|
||||
"tours.overview.close.description": "Are you sure you want to close the guided tour?",
|
||||
"tours.profile.title": "Guided tour",
|
||||
"tours.profile.description": "You can reset the guided tour at any time.",
|
||||
"tours.profile.reset": "Reset guided tour",
|
||||
"tours.profile.notification.success.reset": "Guided tour reset",
|
||||
"widget.profile.title": "Profile"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user