From e3414fee036fd4f34ad4af1364e8b994bb999985 Mon Sep 17 00:00:00 2001 From: RXRD <118821868+RiXelanya@users.noreply.github.com> Date: Sat, 2 Dec 2023 06:30:54 +0700 Subject: [PATCH] fix: add editors to timeline creation (#1918) * fix: streamline timeline creation * fix * streamline ui * state sharing * fix * format fix * lint fix * fix * format fix --- .../BasicExperienceEditor.tsx | 443 +++++++++ .../ExperienceAdditionalEditor.tsx | 427 +++++++++ .../ExperienceAdminEditor.tsx | 289 ++++++ .../ExperienceEditor/ExperienceEditor.tsx | 872 ++---------------- src/interfaces/experience.ts | 1 + src/locale/en.json | 1 + src/locale/fra.json | 1 + 7 files changed, 1234 insertions(+), 800 deletions(-) create mode 100644 src/components/ExperienceEditor/BasicExperienceEditor.tsx create mode 100644 src/components/ExperienceEditor/ExperienceAdditionalEditor.tsx create mode 100644 src/components/ExperienceEditor/ExperienceAdminEditor.tsx diff --git a/src/components/ExperienceEditor/BasicExperienceEditor.tsx b/src/components/ExperienceEditor/BasicExperienceEditor.tsx new file mode 100644 index 000000000..e3255ae95 --- /dev/null +++ b/src/components/ExperienceEditor/BasicExperienceEditor.tsx @@ -0,0 +1,443 @@ +import { + SearchIcon, + XCircleIcon, + PlusCircleIcon, + ChevronDownIcon, +} from '@heroicons/react/solid'; + +import React, { useState, useRef } from 'react'; + +import { + Button, + FormControl, + FormHelperText, + IconButton, + InputLabel, + OutlinedInput, + SvgIcon, + TextField, + Typography, +} from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import { + Autocomplete, + AutocompleteChangeReason, + AutocompleteRenderOptionState, +} from '@material-ui/lab'; + +import { ExperienceProps, VisibilityItem } from '../../interfaces/experience'; +import { Dropzone } from '../atoms/Dropzone'; +import { ListItemPeopleComponent } from '../atoms/ListItem/ListItemPeople'; +import { Loading } from '../atoms/Loading'; +import ShowIf from '../common/show-if.component'; +import { useStyles } from './Experience.styles'; + +import { debounce, isEmpty } from 'lodash'; +import { useSearchHook } from 'src/hooks/use-search.hooks'; +import { User } from 'src/interfaces/user'; +import i18n from 'src/locale'; + +type BasicExperienceEditorProps = { + experience?: ExperienceProps; + handleImageUpload: (files: File[]) => Promise; + onStage: (value: number) => void; + onExperience: (value: any) => void; + onSearchUser?: (query: string) => void; + users?: User[]; + quick?: boolean; + experienceVisibility?: VisibilityItem; + newExperience: ExperienceProps; + onVisibility: (value: any) => void; + image: string; + selectedVisibility: VisibilityItem; + selectedUserIds: User[]; + onSelectedUserIds: (value: any) => void; +}; + +const DEFAULT_EXPERIENCE: ExperienceProps = { + name: '', + allowedTags: [], + people: [], + prohibitedTags: [], + visibility: '', + selectedUserIds: [], +}; + +export const BasicExperienceEditor: React.FC = + props => { + const { + experience = DEFAULT_EXPERIENCE, + handleImageUpload, + onSearchUser, + onStage, + onExperience, + users, + quick = false, + newExperience, + image, + onVisibility, + selectedVisibility, + selectedUserIds, + onSelectedUserIds, + } = props; + const styles = useStyles({ quick }); + const { clearUsers } = useSearchHook(); + + const ref = useRef(null); + const [, setDetailChanged] = useState(false); + const [isLoading] = useState(false); + const [isLoadingSelectedUser] = useState(false); + const [errors] = useState({ + name: false, + picture: false, + tags: false, + people: false, + visibility: false, + selectedUserId: false, + }); + + const handleChange = + (field: keyof ExperienceProps) => + (event: React.ChangeEvent) => { + const value = event.target.value.trimStart(); + + onExperience(prevExperience => ({ + ...prevExperience, + [field]: value, + })); + + setDetailChanged(experience[field] !== value); + }; + + const handleNext = () => { + const number = 2; + onStage(number); + }; + + const visibilityList: VisibilityItem[] = [ + { + id: 'public', + name: i18n.t('Experience.Editor.Visibility.Public'), + }, + { + id: 'private', + name: i18n.t('Experience.Editor.Visibility.OnlyMe'), + }, + { + id: 'selected_user', + name: i18n.t('Experience.Editor.Visibility.Custom'), + }, + { + id: 'friend', + name: i18n.t('Experience.Editor.Visibility.Friend_Only'), + }, + ]; + + const handleVisibilityChange = ( + // eslint-disable-next-line @typescript-eslint/ban-types + event: React.ChangeEvent<{}>, + value: VisibilityItem, + reason: AutocompleteChangeReason, + ) => { + onVisibility(value); + onExperience(prevExperience => ({ + ...prevExperience, + visibility: value?.id, + })); + + setDetailChanged(true); + }; + + const handleSearchUser = (event: React.ChangeEvent) => { + const debounceSubmit = debounce(() => { + onSearchUser(event.target.value); + }, 300); + + debounceSubmit(); + }; + + const clearSearchedUser = () => { + const debounceSubmit = debounce(() => { + onSearchUser(''); + }, 300); + + debounceSubmit(); + }; + + const handleVisibilityPeopleChange = ( + // eslint-disable-next-line @typescript-eslint/ban-types + event: React.ChangeEvent<{}>, + value: User[], + reason: AutocompleteChangeReason, + ) => { + const people = selectedUserIds ? selectedUserIds : []; + console.log({ value }); + if (reason === 'select-option') { + onSelectedUserIds([ + ...people, + ...value.filter(option => people.indexOf(option) === -1), + ]); + clearSearchedUser(); + clearUsers(); + } + + setDetailChanged(true); + }; + + const removeVisibilityPeople = (selected: User) => () => { + onSelectedUserIds( + selectedUserIds + ? selectedUserIds.filter(people => people.id != selected.id) + : [], + ); + + setDetailChanged(true); + }; + + return ( +
+ +
+
+ + {i18n.t(`Experience.Editor.Header`)} + + + {i18n.t(`Experience.Editor.Sub_Header`)} + +
+ + + +
+
+ +
+
+ + + +
+ +
+
+
+
+
+ + + {i18n.t('Experience.Editor.Subtitle_1')} + + + + {i18n.t('Experience.Editor.Helper.Name')} + + + {newExperience?.name.length ?? 0}/50 + + + + option.name} + getOptionSelected={(option, value) => option?.id === value.id} + autoHighlight={false} + disableClearable + onChange={handleVisibilityChange} + value={selectedVisibility || null} + popupIcon={ + + } + renderInput={({ inputProps, ...rest }) => ( + + )} + /> + + {selectedVisibility?.id === 'selected_user' && ( + <> + option.id === value.id} + filterSelectedOptions={true} + getOptionLabel={option => `${option.username} ${option.name}`} + disableClearable + autoHighlight={false} + popupIcon={ + + } + onChange={handleVisibilityPeopleChange} + renderTags={() => null} + renderInput={params => ( + + {params.InputProps.endAdornment} + + ), + }} + helperText={i18n.t('Experience.Editor.Helper.People')} + /> + )} + renderOption={( + option, + state: AutocompleteRenderOptionState, + ) => { + if (option.id === '') return null; + return ( +
+ + @{option.username} + + } + avatar={option.profilePictureURL} + platform={'myriad'} + action={ + + {state.selected ? ( + + ) : ( + + )} + + } + /> +
+ ); + }} + /> + +
+
+ + + + {selectedUserIds + .filter(people => !isEmpty(people.id)) + .map(people => ( + + @{people.username} + + } + avatar={people.profilePictureURL} + platform={'myriad'} + action={ + + + + } + /> + ))} +
+
+ + )} + + + + {i18n.t('Experience.Editor.Subtitle_2')} + + + +   + + + {newExperience?.description?.length ?? 0}/280 + + +
+
+
+ ); + }; diff --git a/src/components/ExperienceEditor/ExperienceAdditionalEditor.tsx b/src/components/ExperienceEditor/ExperienceAdditionalEditor.tsx new file mode 100644 index 000000000..01bc1a99d --- /dev/null +++ b/src/components/ExperienceEditor/ExperienceAdditionalEditor.tsx @@ -0,0 +1,427 @@ +import { + SearchIcon, + XCircleIcon, + PlusCircleIcon, +} from '@heroicons/react/solid'; + +import React, { useState, useRef } from 'react'; + +import { + Button, + FormControl, + IconButton, + SvgIcon, + TextField, + Typography, +} from '@material-ui/core'; +import { + Autocomplete, + AutocompleteChangeReason, + AutocompleteRenderOptionState, +} from '@material-ui/lab'; + +import { ExperienceProps, Tag } from '../../interfaces/experience'; +import { People } from '../../interfaces/people'; +import { ListItemPeopleComponent } from '../atoms/ListItem/ListItemPeople'; +import ShowIf from '../common/show-if.component'; +import { useStyles } from './Experience.styles'; + +import { debounce } from 'lodash'; +import i18n from 'src/locale'; + +type ExperienceAdditionalEditorProps = { + type?: 'Clone' | 'Edit' | 'Create'; + isEdit?: boolean; + experience?: ExperienceProps; + tags: Tag[]; + people: People[]; + onSearchTags: (query: string) => void; + onSearchPeople: (query: string) => void; + onExperience: (value: any) => void; + onStage: (value: number) => void; + quick?: boolean; + showAdvance?: boolean; + newExperience: ExperienceProps; + saveExperience: () => void; +}; + +enum TagsProps { + ALLOWED = 'allowed', + PROHIBITED = 'prohibited', +} + +const DEFAULT_EXPERIENCE: ExperienceProps = { + name: '', + allowedTags: [], + people: [], + prohibitedTags: [], + visibility: '', + selectedUserIds: [], +}; + +export const ExperienceAdditionalEditor: React.FC = + props => { + const { + type = 'Create', + experience = DEFAULT_EXPERIENCE, + people, + tags, + onSearchTags, + onSearchPeople, + quick = false, + onStage, + onExperience, + newExperience, + saveExperience, + } = props; + const styles = useStyles({ quick }); + const ref = useRef(null); + const [, setDetailChanged] = useState(false); + const [errors] = useState({ + name: false, + picture: false, + tags: false, + people: false, + visibility: false, + selectedUserId: false, + }); + + const handleSearchTags = (event: React.ChangeEvent) => { + const debounceSubmit = debounce(() => { + onSearchTags(event.target.value); + }, 300); + + debounceSubmit(); + }; + + const handleSearchPeople = (event: React.ChangeEvent) => { + const debounceSubmit = debounce(() => { + onSearchPeople(event.target.value); + }, 300); + + debounceSubmit(); + }; + + const clearSearchedPeople = () => { + const debounceSubmit = debounce(() => { + onSearchPeople(''); + }, 300); + + debounceSubmit(); + }; + + const handleTagsInputChange = ( + // eslint-disable-next-line @typescript-eslint/ban-types + event: React.ChangeEvent<{}>, + newValue: string, + type: TagsProps, + ) => { + const options = newValue.split(/[ ,]+/); + + let tmpTags: string[] = []; + if (type === TagsProps.ALLOWED) { + tmpTags = newExperience.allowedTags; + } else if (type === TagsProps.PROHIBITED) { + tmpTags = newExperience.prohibitedTags ?? []; + } + + const fieldValue = tmpTags + .concat(options) + .map(x => x.trim()) + .filter(x => x); + + if (options.length > 1) { + handleTagsChange(event, fieldValue, 'create-option', type); + } + }; + + const handleTagsChange = ( + // eslint-disable-next-line @typescript-eslint/ban-types + event: React.ChangeEvent<{}>, + value: string[], + reason: AutocompleteChangeReason, + type: TagsProps, + ) => { + const data = [...new Set(value.map(tag => tag.replace('#', '')))]; + + const prohibitedTagsChanged = + type === TagsProps.PROHIBITED && + (data.filter( + tag => + !experience?.prohibitedTags || + !experience.prohibitedTags.includes(tag), + ).length > 0 || + experience?.prohibitedTags?.length !== data.length); + const allowedTagsChanged = + type === TagsProps.ALLOWED && + (data.filter(tag => !experience.allowedTags.includes(tag)).length > 0 || + data.length !== experience.allowedTags.length); + + setDetailChanged(prohibitedTagsChanged || allowedTagsChanged); + + if (reason === 'remove-option') { + if (type === TagsProps.ALLOWED) { + onExperience(prevExperience => ({ + ...prevExperience, + allowedTags: data, + })); + } else if (type === TagsProps.PROHIBITED) { + onExperience(prevExperience => ({ + ...prevExperience, + prohibitedTags: data, + })); + } + } + + if (reason === 'create-option') { + if (type === TagsProps.ALLOWED) { + onExperience(prevExperience => ({ + ...prevExperience, + allowedTags: data, + })); + } else if (type === TagsProps.PROHIBITED) { + onExperience(prevExperience => ({ + ...prevExperience, + prohibitedTags: data, + })); + } + } + + if (reason === 'select-option') { + if (type === TagsProps.ALLOWED) { + onExperience(prevExperience => ({ + ...prevExperience, + allowedTags: data, + })); + } else if (type === TagsProps.PROHIBITED) { + onExperience(prevExperience => ({ + ...prevExperience, + prohibitedTags: data, + })); + } + } + }; + + const handlePeopleChange = ( + // eslint-disable-next-line @typescript-eslint/ban-types + event: React.ChangeEvent<{}>, + value: People[], + reason: AutocompleteChangeReason, + ) => { + const people = newExperience?.people ? newExperience.people : []; + if (reason === 'select-option') { + onExperience(prevExperience => ({ + ...prevExperience, + people: [ + ...people, + ...value.filter(option => people.indexOf(option) === -1), + ], + })); + clearSearchedPeople(); + } + + setDetailChanged(true); + }; + + const handleBack = () => { + onStage(2); + }; + + return ( +
+ +
+ + + +
+ + {i18n.t(`Experience.Editor.Header`)} + + + {i18n.t(`Experience.Editor.Sub_Header`)} + +
+ + + +
+
+ +
+
+ + className={styles.fill} + id="experience-tags-include" + freeSolo + multiple + value={newExperience.allowedTags ?? []} + options={tags + .map(tag => tag.id) + .filter(tag => !newExperience.allowedTags.includes(tag))} + disableClearable + onChange={(event, value, reason) => { + handleTagsChange(event, value, reason, TagsProps.ALLOWED); + }} + onInputChange={(event, value) => { + handleTagsInputChange(event, value, TagsProps.ALLOWED); + }} + getOptionLabel={option => `#${option}`} + renderInput={params => ( + + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + + tag.id) + .filter(tag => !newExperience.prohibitedTags?.includes(tag))} + disableClearable + onChange={(event, value, reason) => { + handleTagsChange(event, value, reason, TagsProps.PROHIBITED); + }} + onInputChange={(event, value) => { + handleTagsInputChange(event, value, TagsProps.PROHIBITED); + }} + getOptionLabel={option => `#${option}`} + renderInput={params => ( + + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + + option.id === value.id} + filterSelectedOptions={true} + getOptionLabel={option => `${option.username} ${option.name}`} + disableClearable + autoHighlight={false} + popupIcon={ + + } + onChange={handlePeopleChange} + renderTags={() => null} + renderInput={params => ( + + {params.InputProps.endAdornment} + + ), + }} + helperText={''} + /> + )} + renderOption={(option, state: AutocompleteRenderOptionState) => { + if (option.id === '') return null; + return ( +
+ + @{option.username} + + } + avatar={option.profilePictureURL} + platform={option.platform} + action={ + + {state.selected ? ( + + ) : ( + + )} + + } + /> +
+ ); + }} + /> +
+
+
+ ); + }; diff --git a/src/components/ExperienceEditor/ExperienceAdminEditor.tsx b/src/components/ExperienceEditor/ExperienceAdminEditor.tsx new file mode 100644 index 000000000..1a0156b65 --- /dev/null +++ b/src/components/ExperienceEditor/ExperienceAdminEditor.tsx @@ -0,0 +1,289 @@ +import { + SearchIcon, + XCircleIcon, + PlusCircleIcon, +} from '@heroicons/react/solid'; + +import React, { useState, useRef } from 'react'; + +import { + Button, + FormControl, + IconButton, + SvgIcon, + TextField, + Typography, +} from '@material-ui/core'; +import { + Autocomplete, + AutocompleteChangeReason, + AutocompleteRenderOptionState, +} from '@material-ui/lab'; + +import { ExperienceProps } from '../../interfaces/experience'; +import { ListItemPeopleComponent } from '../atoms/ListItem/ListItemPeople'; +import { Loading } from '../atoms/Loading'; +import ShowIf from '../common/show-if.component'; +import { useStyles } from './Experience.styles'; + +import { debounce, isEmpty } from 'lodash'; +import { useSearchHook } from 'src/hooks/use-search.hooks'; +import { User } from 'src/interfaces/user'; +import i18n from 'src/locale'; + +type AdminExperienceEditorProps = { + type?: 'Clone' | 'Edit' | 'Create'; + isEdit?: boolean; + experience?: ExperienceProps; + onStage: (value: number) => void; + onSearchUser?: (query: string) => void; + users?: User[]; + quick?: boolean; + showAdvance?: boolean; + editors: User[]; + setEditors: (editors: User[]) => void; + onExperience: (value: any) => void; +}; + +export const AdminExperienceEditor: React.FC = + props => { + const { + onSearchUser, + users, + quick = false, + onStage, + editors, + setEditors, + onExperience, + } = props; + const styles = useStyles({ quick }); + const { clearUsers } = useSearchHook(); + + const ref = useRef(null); + const [, setDetailChanged] = useState(false); + const [isLoadingSelectedUser] = useState(false); + const [errors] = useState({ + name: false, + picture: false, + tags: false, + people: false, + visibility: false, + selectedUserId: false, + }); + + const handleBack = () => { + onExperience(prevExperience => ({ + ...prevExperience, + editorsId: editors.map(user => user.id), + })); + onStage(1); + }; + + const handleNext = () => { + onExperience(prevExperience => ({ + ...prevExperience, + editorsId: editors.map(user => user.id), + })); + onStage(3); + }; + + const handleSearchUser = (event: React.ChangeEvent) => { + const debounceSubmit = debounce(() => { + onSearchUser(event.target.value); + }, 300); + + debounceSubmit(); + }; + + const clearSearchedUser = () => { + const debounceSubmit = debounce(() => { + onSearchUser(''); + }, 300); + + debounceSubmit(); + }; + + const handleEditorsPeopleChange = ( + // eslint-disable-next-line @typescript-eslint/ban-types + event: React.ChangeEvent<{}>, + value: User[], + reason: AutocompleteChangeReason, + ) => { + const people = editors ? editors : []; + console.log({ value }); + if (reason === 'select-option') { + setEditors([ + ...people, + ...value.filter(option => people.indexOf(option) === -1), + ]); + clearSearchedUser(); + clearUsers(); + } + + // setDetailChanged(true); + }; + + const removeEditorsPeople = (selected: User) => () => { + setEditors( + editors ? editors.filter(people => people.id != selected.id) : [], + ); + + setDetailChanged(true); + }; + + return ( +
+ +
+ + + +
+ + {i18n.t(`Experience.Editor.Header`)} + + + {i18n.t(`Experience.Editor.Sub_Header`)} + +
+ + + +
+
+ +
+
+ <> + option.id === value.id} + filterSelectedOptions={true} + getOptionLabel={option => `${option.username} ${option.name}`} + disableClearable + autoHighlight={false} + popupIcon={ + + } + onChange={handleEditorsPeopleChange} + renderTags={() => null} + renderInput={params => ( + + {params.InputProps.endAdornment} + + ), + }} + helperText={i18n.t('Experience.Editor.Helper.People')} + /> + )} + renderOption={( + option, + state: AutocompleteRenderOptionState, + ) => { + if (option.id === '') return null; + return ( +
+ + @{option.username} + + } + avatar={option.profilePictureURL} + platform={'myriad'} + action={ + + {state.selected ? ( + + ) : ( + + )} + + } + /> +
+ ); + }} + /> + +
+
+ + + + {editors + .filter(people => !isEmpty(people.id)) + .map(people => ( + + @{people.username} + + } + avatar={people.profilePictureURL} + platform={'myriad'} + action={ + + + + } + /> + ))} +
+
+ +
+
+
+ ); + }; diff --git a/src/components/ExperienceEditor/ExperienceEditor.tsx b/src/components/ExperienceEditor/ExperienceEditor.tsx index 1e0d5d81d..a6b72508e 100644 --- a/src/components/ExperienceEditor/ExperienceEditor.tsx +++ b/src/components/ExperienceEditor/ExperienceEditor.tsx @@ -1,35 +1,8 @@ -import { - SearchIcon, - XCircleIcon, - PlusCircleIcon, - ChevronDownIcon, - CogIcon, - ChevronUpIcon, -} from '@heroicons/react/solid'; - import React, { useState, useEffect, useRef } from 'react'; -import InfiniteScroll from 'react-infinite-scroll-component'; -import { useSelector } from 'react-redux'; import { useRouter } from 'next/router'; -import { - Button, - FormControl, - FormHelperText, - IconButton, - InputLabel, - OutlinedInput, - SvgIcon, - TextField, - Typography, -} from '@material-ui/core'; -import CircularProgress from '@material-ui/core/CircularProgress'; -import { - Autocomplete, - AutocompleteChangeReason, - AutocompleteRenderOptionState, -} from '@material-ui/lab'; +import { Button } from '@material-ui/core'; import { ExperienceProps, @@ -38,22 +11,17 @@ import { SelectedUserIds, } from '../../interfaces/experience'; import { People } from '../../interfaces/people'; -import { PostDetailExperience } from '../PostDetailExperience/PostDetailExperience'; -import { Dropzone } from '../atoms/Dropzone'; -import { ListItemPeopleComponent } from '../atoms/ListItem/ListItemPeople'; -import { Loading } from '../atoms/Loading'; import ShowIf from '../common/show-if.component'; +import { BasicExperienceEditor } from './BasicExperienceEditor'; import { useStyles } from './Experience.styles'; +import { ExperienceAdditionalEditor } from './ExperienceAdditionalEditor'; +import { AdminExperienceEditor } from './ExperienceAdminEditor'; -import { debounce, isEmpty } from 'lodash'; +import { isEmpty } from 'lodash'; import { useExperienceHook } from 'src/hooks/use-experience-hook'; -import { useSearchHook } from 'src/hooks/use-search.hooks'; -import { Post } from 'src/interfaces/post'; import { User } from 'src/interfaces/user'; import * as UserAPI from 'src/lib/api/user'; import i18n from 'src/locale'; -import { RootState } from 'src/reducers'; -import { UserState } from 'src/reducers/user/reducer'; type ExperienceEditorProps = { type?: 'Clone' | 'Edit' | 'Create'; @@ -72,11 +40,6 @@ type ExperienceEditorProps = { experienceVisibility?: VisibilityItem; }; -enum TagsProps { - ALLOWED = 'allowed', - PROHIBITED = 'prohibited', -} - const DEFAULT_EXPERIENCE: ExperienceProps = { name: '', allowedTags: [], @@ -104,37 +67,27 @@ export const ExperienceEditor: React.FC = props => { } = props; const styles = useStyles({ quick }); - const { - experiencePosts, - hasMore, - loadPostExperience, - loadNextPostExperience, - loadExperiencePostList, - addPostsToExperience, - } = useExperienceHook(); + const { loadPostExperience } = useExperienceHook(); const router = useRouter(); - const { clearUsers } = useSearchHook(); const ref = useRef(null); - const { anonymous, user } = useSelector( - state => state.userState, - ); - const [experienceId, setExperienceId] = useState(); + const [, setExperienceId] = useState(); const [newExperience, setNewExperience] = useState(experience); const [image, setImage] = useState( experience?.experienceImageURL, ); const [, setDetailChanged] = useState(false); - const [isLoading, setIsloading] = useState(false); + const [stage, setStage] = useState(1); + const [, setIsloading] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false); const [selectedVisibility, setSelectedVisibility] = useState(experienceVisibility); const [selectedUserIds, setSelectedUserIds] = useState([]); + const [editors, setEditors] = useState([]); const [pageUserIds, setPageUserIds] = React.useState(1); - const [isLoadingSelectedUser, setIsLoadingSelectedUser] = - useState(false); - const [errors, setErrors] = useState({ + const [, setIsLoadingSelectedUser] = useState(false); + const [, setErrors] = useState({ name: false, picture: false, tags: false, @@ -159,30 +112,6 @@ export const ExperienceEditor: React.FC = props => { } }, [isSubmitted, newExperience]); - const handleSearchTags = (event: React.ChangeEvent) => { - const debounceSubmit = debounce(() => { - onSearchTags(event.target.value); - }, 300); - - debounceSubmit(); - }; - - const handleSearchPeople = (event: React.ChangeEvent) => { - const debounceSubmit = debounce(() => { - onSearchPeople(event.target.value); - }, 300); - - debounceSubmit(); - }; - - const clearSearchedPeople = () => { - const debounceSubmit = debounce(() => { - onSearchPeople(''); - }, 300); - - debounceSubmit(); - }; - const handleImageUpload = async (files: File[]) => { if (files.length > 0) { setIsloading(true); @@ -198,141 +127,8 @@ export const ExperienceEditor: React.FC = props => { setDetailChanged(true); }; - const handleChange = - (field: keyof ExperienceProps) => - (event: React.ChangeEvent) => { - const value = event.target.value.trimStart(); - - setNewExperience(prevExperience => ({ - ...prevExperience, - [field]: value, - })); - - setDetailChanged(experience[field] !== value); - }; - - const handleTagsInputChange = ( - // eslint-disable-next-line @typescript-eslint/ban-types - event: React.ChangeEvent<{}>, - newValue: string, - type: TagsProps, - ) => { - const options = newValue.split(/[ ,]+/); - - let tmpTags: string[] = []; - if (type === TagsProps.ALLOWED) { - tmpTags = newExperience.allowedTags; - } else if (type === TagsProps.PROHIBITED) { - tmpTags = newExperience.prohibitedTags ?? []; - } - - const fieldValue = tmpTags - .concat(options) - .map(x => x.trim()) - .filter(x => x); - - if (options.length > 1) { - handleTagsChange(event, fieldValue, 'create-option', type); - } - }; - - const handleTagsChange = ( - // eslint-disable-next-line @typescript-eslint/ban-types - event: React.ChangeEvent<{}>, - value: string[], - reason: AutocompleteChangeReason, - type: TagsProps, - ) => { - const data = [...new Set(value.map(tag => tag.replace('#', '')))]; - - const prohibitedTagsChanged = - type === TagsProps.PROHIBITED && - (data.filter( - tag => - !experience?.prohibitedTags || - !experience.prohibitedTags.includes(tag), - ).length > 0 || - experience?.prohibitedTags?.length !== data.length); - const allowedTagsChanged = - type === TagsProps.ALLOWED && - (data.filter(tag => !experience.allowedTags.includes(tag)).length > 0 || - data.length !== experience.allowedTags.length); - - setDetailChanged(prohibitedTagsChanged || allowedTagsChanged); - - if (reason === 'remove-option') { - if (type === TagsProps.ALLOWED) { - setNewExperience(prevExperience => ({ - ...prevExperience, - allowedTags: data, - })); - } else if (type === TagsProps.PROHIBITED) { - setNewExperience(prevExperience => ({ - ...prevExperience, - prohibitedTags: data, - })); - } - } - - if (reason === 'create-option') { - if (type === TagsProps.ALLOWED) { - setNewExperience(prevExperience => ({ - ...prevExperience, - allowedTags: data, - })); - } else if (type === TagsProps.PROHIBITED) { - setNewExperience(prevExperience => ({ - ...prevExperience, - prohibitedTags: data, - })); - } - } - - if (reason === 'select-option') { - if (type === TagsProps.ALLOWED) { - setNewExperience(prevExperience => ({ - ...prevExperience, - allowedTags: data, - })); - } else if (type === TagsProps.PROHIBITED) { - setNewExperience(prevExperience => ({ - ...prevExperience, - prohibitedTags: data, - })); - } - } - }; - - const handlePeopleChange = ( - // eslint-disable-next-line @typescript-eslint/ban-types - event: React.ChangeEvent<{}>, - value: People[], - reason: AutocompleteChangeReason, - ) => { - const people = newExperience?.people ? newExperience.people : []; - if (reason === 'select-option') { - setNewExperience(prevExperience => ({ - ...prevExperience, - people: [ - ...people, - ...value.filter(option => people.indexOf(option) === -1), - ], - })); - clearSearchedPeople(); - } - - setDetailChanged(true); - }; - - const removeSelectedPeople = (selected: People) => () => { - setNewExperience(prevExperience => ({ - ...prevExperience, - people: prevExperience?.people - ? prevExperience?.people.filter(people => people.id != selected.id) - : [], - })); - - setDetailChanged(true); + const onStage = (value: number) => { + setStage(value); }; const validateExperience = (): boolean => { @@ -342,10 +138,10 @@ export const ExperienceEditor: React.FC = props => { const validPeople = newExperience.people.filter(people => !isEmpty(people.id)).length >= 0; const validSelectedUserIds = - selectedVisibility && selectedVisibility?.id === 'selected_user' + newExperience.visibility === 'selected_user' ? selectedUserIds.length > 0 - : !isEmpty(selectedVisibility?.id); - const validVisibility = !isEmpty(selectedVisibility?.id); + : !isEmpty(newExperience.visibility); + const validVisibility = !isEmpty(newExperience.visibility); setErrors({ name: !validName, @@ -371,6 +167,7 @@ export const ExperienceEditor: React.FC = props => { const valid = validateExperience(); if (valid) { + console.log(newExperience); onSave(newExperience); } else { ref.current?.scrollIntoView({ @@ -380,32 +177,6 @@ export const ExperienceEditor: React.FC = props => { } }; - const handleNextPagePosts = () => { - if (experienceId) { - loadNextPostExperience(experienceId); - } - }; - - const handleRemoveFromExperience = (post: Post) => { - loadExperiencePostList(post.id, postsExperiences => { - const tmpListExperience: string[] = []; - postsExperiences.map(item => { - if (item.posts) { - tmpListExperience.push(item.id); - } - }); - if (experienceId) { - const indexExperience = tmpListExperience.indexOf(experienceId); - if (indexExperience > -1) { - tmpListExperience.splice(indexExperience, 1); - } - addPostsToExperience(post.id, tmpListExperience, () => { - loadPostExperience(experienceId); - }); - } - }); - }; - const visibilityList: VisibilityItem[] = [ { id: 'public', @@ -425,67 +196,6 @@ export const ExperienceEditor: React.FC = props => { }, ]; - const handleVisibilityChange = ( - // eslint-disable-next-line @typescript-eslint/ban-types - event: React.ChangeEvent<{}>, - value: VisibilityItem, - reason: AutocompleteChangeReason, - ) => { - setSelectedVisibility(value); - setNewExperience(prevExperience => ({ - ...prevExperience, - visibility: value?.id, - })); - - setDetailChanged(true); - }; - - const handleSearchUser = (event: React.ChangeEvent) => { - const debounceSubmit = debounce(() => { - onSearchUser(event.target.value); - }, 300); - - debounceSubmit(); - }; - - const clearSearchedUser = () => { - const debounceSubmit = debounce(() => { - onSearchUser(''); - }, 300); - - debounceSubmit(); - }; - - const handleVisibilityPeopleChange = ( - // eslint-disable-next-line @typescript-eslint/ban-types - event: React.ChangeEvent<{}>, - value: User[], - reason: AutocompleteChangeReason, - ) => { - const people = selectedUserIds ? selectedUserIds : []; - console.log({ value }); - if (reason === 'select-option') { - setSelectedUserIds([ - ...people, - ...value.filter(option => people.indexOf(option) === -1), - ]); - clearSearchedUser(); - clearUsers(); - } - - setDetailChanged(true); - }; - - const removeVisibilityPeople = (selected: User) => () => { - setSelectedUserIds( - selectedUserIds - ? selectedUserIds.filter(people => people.id != selected.id) - : [], - ); - - setDetailChanged(true); - }; - const mappingUserIds = () => { console.log({ selectedVisibility }); if (selectedVisibility?.id === 'selected_user') { @@ -510,6 +220,14 @@ export const ExperienceEditor: React.FC = props => { } }; + const onExperience = value => { + setNewExperience(value); + }; + + const onVisibility = value => { + setSelectedVisibility(value); + }; + useEffect(() => { mappingUserIds(); setDetailChanged(true); @@ -548,500 +266,54 @@ export const ExperienceEditor: React.FC = props => { setShowAdvanceSetting(!showAdvanceSetting); }; - return ( -
- -
-
- - {i18n.t(`Experience.Editor.Header`)} - - - {i18n.t(`Experience.Editor.Sub_Header`)} - -
- - - -
-
- -
-
- - - -
- -
-
-
-
-
- - - {i18n.t('Experience.Editor.Subtitle_1')} - - - - {i18n.t('Experience.Editor.Helper.Name')} - - - {newExperience?.name.length ?? 0}/50 - - - - option.name} - getOptionSelected={(option, value) => option?.id === value.id} - autoHighlight={false} - disableClearable - onChange={handleVisibilityChange} - value={selectedVisibility || null} - popupIcon={ - - } - renderInput={({ inputProps, ...rest }) => ( - - )} - /> - - {selectedVisibility?.id === 'selected_user' && ( - <> - option.id === value.id} - filterSelectedOptions={true} - getOptionLabel={option => `${option.username} ${option.name}`} - disableClearable - autoHighlight={false} - popupIcon={ - - } - onChange={handleVisibilityPeopleChange} - renderTags={() => null} - renderInput={params => ( - - {params.InputProps.endAdornment} - - ), - }} - helperText={i18n.t('Experience.Editor.Helper.People')} - /> - )} - renderOption={( - option, - state: AutocompleteRenderOptionState, - ) => { - if (option.id === '') return null; - return ( -
- - @{option.username} - - } - avatar={option.profilePictureURL} - platform={'myriad'} - action={ - - {state.selected ? ( - - ) : ( - - )} - - } - /> -
- ); - }} - /> - -
-
- - - - {selectedUserIds - .filter(people => !isEmpty(people.id)) - .map(people => ( - - @{people.username} - - } - avatar={people.profilePictureURL} - platform={'myriad'} - action={ - - - - } - /> - ))} -
-
- - )} - - - - {i18n.t('Experience.Editor.Subtitle_2')} - - - -   - - - {newExperience?.description?.length ?? 0}/280 - - - - - - - - - - className={styles.fill} - id="experience-tags-include" - freeSolo - multiple - value={newExperience.allowedTags ?? []} - options={tags - .map(tag => tag.id) - .filter(tag => !newExperience.allowedTags.includes(tag))} - disableClearable - onChange={(event, value, reason) => { - handleTagsChange(event, value, reason, TagsProps.ALLOWED); - }} - onInputChange={(event, value) => { - handleTagsInputChange(event, value, TagsProps.ALLOWED); - }} - getOptionLabel={option => `#${option}`} - renderInput={params => ( - - {params.InputProps.endAdornment} - - ), - }} - /> - )} - /> - - tag.id) - .filter(tag => !newExperience.prohibitedTags?.includes(tag))} - disableClearable - onChange={(event, value, reason) => { - handleTagsChange(event, value, reason, TagsProps.PROHIBITED); - }} - onInputChange={(event, value) => { - handleTagsInputChange(event, value, TagsProps.PROHIBITED); - }} - getOptionLabel={option => `#${option}`} - renderInput={params => ( - - {params.InputProps.endAdornment} - - ), - }} - /> - )} - /> - - option.id === value.id} - filterSelectedOptions={true} - getOptionLabel={option => `${option.username} ${option.name}`} - disableClearable - autoHighlight={false} - popupIcon={ - - } - onChange={handlePeopleChange} - renderTags={() => null} - renderInput={params => ( - - {params.InputProps.endAdornment} - - ), - }} - helperText={''} - /> - )} - renderOption={(option, state: AutocompleteRenderOptionState) => { - if (option.id === '') return null; - return ( -
- - @{option.username} - - } - avatar={option.profilePictureURL} - platform={option.platform} - action={ - - {state.selected ? ( - - ) : ( - - )} - - } - /> -
- ); - }} - /> + if (stage === 1) { + return ( + + ); + } -
- {newExperience.people - .filter(people => !isEmpty(people.id)) - .map(people => ( - - @{people.username} - - } - avatar={people.profilePictureURL} - platform={people.platform} - action={ - - - - } - /> - ))} -
+ if (stage === 2) { + return ( + + ); + } - }> - {experiencePosts.length === 0 ? ( -
- - {i18n.t('Experience.Editor.Post.Title')} - - - {i18n.t('Experience.Editor.Post.Desc')} - -
- ) : ( - <> - - {i18n.t('Experience.Editor.Post.Title')} - - {experiencePosts.map(post => ( - null} - type={'default'} - onRemoveFromExperience={() => - handleRemoveFromExperience(post) - } - /> - ))} - - )} -
-
-
-
+ if (stage === 3) { + return ( + + ); + } + return ( + <>
@@ -1062,6 +334,6 @@ export const ExperienceEditor: React.FC = props => {
-
+ ); }; diff --git a/src/interfaces/experience.ts b/src/interfaces/experience.ts index 5d81e5818..ab76afd99 100644 --- a/src/interfaces/experience.ts +++ b/src/interfaces/experience.ts @@ -43,6 +43,7 @@ export interface ExperienceProps extends Searchable { people: People[]; visibility: string; selectedUserIds: SelectedUserIds[]; + editorsId?: string[]; } export interface Experience extends ExperienceProps, BaseModel { diff --git a/src/locale/en.json b/src/locale/en.json index 07a970011..ca6c7a8ef 100644 --- a/src/locale/en.json +++ b/src/locale/en.json @@ -111,6 +111,7 @@ "Placeholder_3": "Search people here", "Placeholder_4": "Select Type of Privacy", "Placeholder_5": "Custom People Who Can View", + "Placeholder_6": "Who can edit timeline", "Post": { "Title": "Post", "Desc": "Manually added posts will appear here. You can manually add a post to an Timeline by selecting 'add post to Timeline' on the top-right menu of each post." diff --git a/src/locale/fra.json b/src/locale/fra.json index 42c0e2d22..209ecf648 100644 --- a/src/locale/fra.json +++ b/src/locale/fra.json @@ -111,6 +111,7 @@ "Placeholder_3": "Rechercher des personnes ici", "Placeholder_4": "Select Type of Privacy", "Placeholder_5": "Custom People Who Can View", + "Placeholder_6": "Who can edit timeline", "Post": { "Title": "Publier", "Desc": "Les messages ajoutés manuellement apparaîtront ici. Vous pouvez ajouter manuellement une publication à une Timeline en sélectionnant 'Ajouter une publication à la Timeline' dans le menu en haut à droite de chaque publication."