react-redux#shallowEqual JavaScript Examples
The following examples show how to use
react-redux#shallowEqual.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.jsx From react-firebase-admin with MIT License | 6 votes |
DatePickerStyled = ({ date, onChange }) => {
const onDateChangedHandler = (value) =>
onChange(value ? value.toDateString() : new Date().toDateString());
const { locale } = useSelector(
(state) => ({
locale: state.preferences.locale,
}),
shallowEqual
);
return (
<DatePicker
locale={locale}
dateFormat={dateFormat(locale)}
selected={date}
onChange={onDateChangedHandler}
/>
);
}
Example #2
Source File: index.js From rtk-demo with MIT License | 6 votes |
export function useHackerNewsArticles({ limit = 20, offset = 0 } = {}) {
useInjectReducer({ key: name, reducer });
useInjectSaga({ key: name, saga });
const dispatch = useDispatch();
const store = useSelector(makeSelectHackerNewsArticles(), shallowEqual);
useEffect(() => {
if (!store?.data?.length && !store?.loading) {
dispatch(
actions.fetch({
offset,
limit,
}),
);
}
}, []);
return store;
}
Example #3
Source File: DocsIndexContainer.js From signdocs with MIT License | 6 votes |
DocsIndexContainer = () => {
const [loading, setLoading] = useState(true);
const dispatch = useDispatch();
const docs = useSelector(getAllDocuments, isEqual);
useEffect(() => {
(async function getAllDocs() {
setLoading(true);
dispatch(fetchDocuments()).done(() => setLoading(false));
})();
}, []);
const docsArray = docs && Object.keys(docs) ? Object.values(docs) : [];
const currentUser = useSelector(getCurrentUser, shallowEqual);
const DocsIndexOrNoDocs = () =>
docsArray.length > 0 ? (
<DocsIndex docs={docsArray} currentUser={currentUser} />
) : (
<NoDocsCallToCreate />
);
return (
<div id="index-container">
<Sidebar />
<div className="index-inbox">
<div className="search-bar">
<h2>Inbox</h2>
</div>
{loading ? <div>Loading...</div> : <DocsIndexOrNoDocs />}
</div>
</div>
);
}
Example #4
Source File: secretaries-tabs.js From what-front with MIT License | 6 votes |
SecretariesTabs = ({ index }) => {
const { id } = useParams();
const [loadAllSecretaries] = useActions([fetchSecretaries]);
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
useEffect(() => {
loadAllSecretaries();
}, [loadAllSecretaries]);
if (currentUser.role === 4) {
return (
<Tabs defaultIndex={index} className="container col-lg-6 col-md-8 col-sm-12 pt-5" linkBack={paths.SECRETARIES}>
<Tab title="Secretary's details" index={0}>
<SecretarysDetails id={Number(id)} />
</Tab>
<Tab title="Edit secretary">
<EditSecretarysDetails id={Number(id)} />
</Tab>
</Tabs>
);
}
return <SecretarysDetails id={Number(id)} />;
}
Example #5
Source File: AvailableImagesTile.js From edge-frontend with Apache License 2.0 | 6 votes |
AvailableImageTile = ({ onNewImageClick }) => {
const { isLoading, hasError, data } = useSelector(
({ imagesReducer }) => ({
isLoading:
imagesReducer?.isLoading !== undefined
? imagesReducer?.isLoading
: true,
hasError: imagesReducer?.hasError || false,
data: imagesReducer?.data || null,
}),
shallowEqual
);
return (
<AvailableImageTileBase>
<CardBody>
{isLoading ? (
<Bullseye>
<Spinner />
</Bullseye>
) : hasError ? (
data
) : (
<Button variant="link" style={{ paddingLeft: 0 }}>
{data.meta.count} images
</Button>
)}
</CardBody>
<CardFooter>
<Button variant="primary" onClick={() => onNewImageClick()}>
Create new image
</Button>
</CardFooter>
</AvailableImageTileBase>
);
}
Example #6
Source File: index.jsx From react-firebase-admin with MIT License | 6 votes |
NotFound = () => {
const location = useLocation();
const { isAuth } = useSelector(
state => ({
isAuth: !!state.auth.userData.id
}),
shallowEqual
);
const userPath = isAuth ? path.ROOT : path.LOGIN;
return (
<section className="hero is-fullheight">
<div className="hero-body">
<section className={`section ${classes.section}`}>
<div className="container">
<div className="columns is-vcentered is-desktop">
<div className="column has-text-centered">
<h1 className="title">{useFormatMessage('NotFound.404')}</h1>
<p className="subtitle">
{useFormatMessage('NotFound.url', { url: location.pathname })}
</p>
<Link className="button is-info is-normal" to={userPath}>
{useFormatMessage('NotFound.back')}
</Link>
</div>
<div className="column has-text-centered">
<img src={NotFoudImage} alt="404 error" />
</div>
</div>
</div>
</section>
</div>
</section>
);
}
Example #7
Source File: hooks.js From frontend-app-course-authoring with GNU Affero General Public License v3.0 | 5 votes |
export function useModel(type, id) {
return useSelector(
state => (state.models[type] !== undefined ? state.models[type][id] : undefined),
shallowEqual,
);
}
Example #8
Source File: secretarys-details.js From what-front with MIT License | 5 votes |
SecretarysDetails = ({ id }) => {
const { data, isLoading, isLoaded } = useSelector(secretariesSelector, shallowEqual);
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
const history = useHistory();
const secretary = data.find((user) => user.id === id);
useEffect(() => {
if (!secretary && isLoaded) {
history.push(paths.NOT_FOUND);
}
}, [secretary, isLoaded, history]);
return (
<div className="container">
<div className="row justify-content-center">
<div className={classNames('col-sm-12 card shadow',
{ 'col-md-12': currentUser.role === 4, 'col-md-6': currentUser.role === 8 })}
>
<div className="px-2 py-4">
<h3>Secretary's details</h3>
<hr />
<WithLoading isLoading={isLoading && !isLoaded} className="d-block mx-auto">
<div className="row">
<div className="col-12 col-md-6 font-weight-bolder">First Name:</div>
<div className="col-12 col-md-6">{secretary?.firstName}</div>
</div>
<hr />
<div className="row">
<div className="col-12 col-md-6 font-weight-bolder">Last Name:</div>
<div className="col-12 col-md-6">{secretary?.lastName}</div>
</div>
<hr />
<div className="row mb-3">
<div className="col-12 col-md-6 font-weight-bolder">Email address:</div>
<div className="col-12 col-md-6">{secretary?.email}</div>
</div>
</WithLoading>
</div>
</div>
</div>
</div>
);
}
Example #9
Source File: index.jsx From react-firebase-admin with MIT License | 5 votes |
Profile = () => {
const { userData } = useSelector(
(state) => ({
userData: state.auth.userData,
}),
shallowEqual
);
const dispatch = useDispatch();
const onSubmitHandler = (value) => {
const newUser = {
...value,
file: value?.file[0] || null,
isEditing: true,
isProfile: true,
id: userData.id,
};
dispatch(modifyUser(newUser));
};
return (
<>
<section className="hero is-hero-bar">
<div className="hero-body">
<h1 className="title">{useFormatMessage('Profile.profile')}</h1>
</div>
</section>
<section className="section is-main-section">
<UserForm
isEditing
isProfile
user={userData}
onSubmitHandler={onSubmitHandler}
schema={schema}
/>
<ChangePassword />
</section>
</>
);
}
Example #10
Source File: courses-tabs.js From what-front with MIT License | 5 votes |
CoursesTabs = ({ index }) => {
const page = useLocation();
const { id } = useParams();
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
const loadActiveCourses = useActions(fetchActiveCourses);
const coursesActiveData = useSelector(coursesActiveSelector, shallowEqual);
const loadNotActiveCourses = useActions(fetchNotActiveCourses);
const coursesNotActiveData = useSelector(coursesNotActiveSelector, shallowEqual);
const isCourseEnable = coursesActiveData.data.map(({ id }) => id).includes(Number(id));
useEffect(() => {
loadActiveCourses();
}, [loadActiveCourses]);
useEffect(() => {
loadNotActiveCourses();
}, [loadNotActiveCourses]);
if (currentUser.role === 8 || currentUser.role === 4) {
if(isCourseEnable){
return (
<Tabs defaultIndex={index} className="container w-50 pt-5" linkBack={paths.COURSES}>
<Tab title="Course details">
<CourseDetails
id={Number(id)}
coursesData={coursesActiveData}
/>
</Tab>
<Tab title="Edit course details">
<EditCourse
id={Number(id)}
coursesData={coursesActiveData}
/>
</Tab>
</Tabs>
);
}
else{
return (
<Tabs defaultIndex={index} className="container w-50 pt-5" linkBack={paths.COURSES}>
<Tab title="Course details">
<CourseDetails
id={Number(id)}
coursesData={coursesNotActiveData}
/>
</Tab>
<Tab title="Edit course details">
<EditCourse
id={Number(id)}
coursesData={coursesNotActiveData}
/>
</Tab>
</Tabs>
);
}
} else {
if(isCourseEnable){
return <CourseDetails id={Number(id)} coursesData={coursesActiveData} />
}
else{
return <CourseDetails id={Number(id)} coursesData={coursesNotActiveData} />
}
}
}
Example #11
Source File: index.jsx From react-firebase-admin with MIT License | 5 votes |
Aside = ({ handleMobileToggle }) => {
const { isAdmin } = useSelector(
(state) => ({
isAdmin: state.auth.userData.isAdmin,
}),
shallowEqual
);
const usersMessage = useFormatMessage('Aside.users');
return (
<aside className="aside is-placed-left is-expanded">
<Link to={paths.ROOT} className="aside-tools">
<div className="aside-tools-label">
<span>
<b>React</b> Firebase
</span>
</div>
</Link>
<div className="menu is-menu-main">
<ul className="menu-list">
<li>
<NavLink
to={paths.ROOT}
className="has-icon"
onClick={handleMobileToggle}
>
<span className="icon">
<i className="mdi mdi-home" />
</span>
<span className="menu-item-label">
{useFormatMessage('Aside.home')}
</span>
</NavLink>
</li>
{isAdmin && (
<li>
<NavLink
to={paths.USERS}
className="has-icon"
onClick={handleMobileToggle}
>
<span className="icon">
<i className="mdi mdi-account-supervisor" />
</span>
<span className="menu-item-label">{usersMessage}</span>
</NavLink>
</li>
)}
<SubMenu label={useFormatMessage('Aside.dropdownMenu')}>
<li>
<NavLink
className={classes.submenuLink}
to={paths.SUBMENU_1}
onClick={handleMobileToggle}
>
<span>{useFormatMessage('Aside.submenu1')}</span>
</NavLink>
</li>
<li>
<NavLink
className={classes.submenuLink}
to={paths.SUBMENU_2}
onClick={handleMobileToggle}
>
<span>{useFormatMessage('Aside.submenu2')}</span>
</NavLink>
</li>
</SubMenu>
</ul>
</div>
</aside>
);
}
Example #12
Source File: students-tabs.js From what-front with MIT License | 5 votes |
StudentsTabs = ({ index }) => {
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
const { id } = useParams();
const [
fetchStudentById,
fetchGroups,
fetchStudentGroups,
fetchStudentLessons,
] = useActions([
loadStudentById,
globalLoadStudentGroups,
loadStudentGroups,
fetchLessonsByStudentId,
]);
useEffect(() => {
fetchStudentById(id);
fetchGroups();
fetchStudentGroups(id);
fetchStudentLessons(id);
}, [
fetchGroups,
fetchStudentById,
fetchStudentGroups,
fetchStudentLessons,
id,
]);
const arrow = (
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-arrow-left"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"
/>
</svg>
);
return (
<>
{currentUser.role === 2 ? (
<section
defaultIndex={index}
className="container w-50 pt-5"
linkBack="/students"
>
<Link
className="nav-item nav-link d-flex align-items-center"
to={{
pathname: '/students',
}}
>
{arrow}
</Link>
<StudentDetails />
</section>
) : (
<Tabs
defaultIndex={index}
className="container w-50 pt-5"
linkBack="/students"
>
<Tab title="Student details">
<StudentDetails />
</Tab>
<Tab title="Edit student details">
<EditStudentsDetails id={id} />
</Tab>
</Tabs>
)}
</>
);
}
Example #13
Source File: LogsTable.js From sed-frontend with Apache License 2.0 | 5 votes |
LogsTable = () => {
const [opened, setOpened] = useState([]);
const dispatch = useDispatch();
const logLoaded = useSelector(
({ logReducer }) => logReducer?.loaded || false
);
const rows = useSelector(({ logReducer }) => logReducer?.results || []);
const pagination = useSelector(
({ logReducer }) => ({
itemCount: logReducer?.total,
perPage: logReducer?.limit,
page:
Math.floor((logReducer?.offset || 0) / (logReducer?.limit || 0)) + 1,
}),
shallowEqual
);
useEffect(() => {
dispatch(fetchLog());
}, []);
const onCollapse = (_e, _key, isOpen, { id }) => {
setOpened(() =>
isOpen ? [...opened, id] : opened.filter((openId) => openId !== id)
);
};
const setPage = useCallback(
(_e, pageNumber) =>
dispatch(fetchLog({ page: pageNumber, perPage: pagination.perPage })),
[dispatch, pagination.perPage]
);
const setPerPage = useCallback(
(_e, perPage) => dispatch(fetchLog({ page: 1, perPage })),
[dispatch]
);
return (
<Fragment>
<PrimaryToolbar
pagination={
logLoaded ? (
{
...pagination,
onSetPage: setPage,
onPerPageSelect: setPerPage,
}
) : (
<Skeleton width="33%" />
)
}
/>
{logLoaded ? (
<Table
aria-label="Logs table"
variant={TableVariant.compact}
rows={rowsMapper(rows, opened)}
cells={columns}
onCollapse={onCollapse}
>
<TableHeader />
<TableBody />
</Table>
) : (
<SkeletonTable colSize={3} rowSize={10} />
)}
<TableToolbar isFooter>
{logLoaded ? (
<Pagination
{...pagination}
variant={PaginationVariant.bottom}
onSetPage={setPage}
onPerPageSelect={setPerPage}
/>
) : (
<Skeleton width="33%" />
)}
</TableToolbar>
</Fragment>
);
}
Example #14
Source File: hooks.js From frontend-app-course-authoring with GNU Affero General Public License v3.0 | 5 votes |
export function useModels(type, ids) {
return useSelector(
state => ids.map(
id => (state.models[type] !== undefined ? state.models[type][id] : undefined),
),
shallowEqual,
);
}
Example #15
Source File: DeviceSummaryTile.js From edge-frontend with Apache License 2.0 | 5 votes |
DeviceSummaryTile = () => {
const { isLoading, hasError, data } = useSelector(
({ deviceSummaryReducer }) => ({
isLoading:
deviceSummaryReducer?.isLoading !== undefined
? deviceSummaryReducer?.isLoading
: true,
hasError: deviceSummaryReducer?.hasError || false,
data: deviceSummaryReducer?.data || null,
}),
shallowEqual
);
if (isLoading) {
return (
<Card className="tiles-card">
<CardTitle>Device summary information</CardTitle>
<CardBody>
<Bullseye>
<Spinner />
</Bullseye>
</CardBody>
</Card>
);
}
if (hasError) {
return (
<Card className="tiles-card">
<CardTitle>Device summary information</CardTitle>
<CardBody>{data}</CardBody>
</Card>
);
}
return (
<DeviceSummaryTileBase
orphaned={data['orphaned']}
active={data['active']}
noReports={data['noReports']}
neverReported={data['neverReported']}
/>
);
}
Example #16
Source File: index.jsx From NeteaseCloudMusic with MIT License | 5 votes |
BannerComp = memo((props) => {
const [currIndex, setCurrIndex] = useState(0);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getBannerListAsyncAction());
}, [dispatch]);
const { banners } = useSelector(
(state) => ({
banners: state.getIn(["recommendReducer", "bannersList"]),
}),
shallowEqual
);
const bannerRef = useRef();
const bannerChange = useCallback((from, to) => {
// console.log(from);
setCurrIndex(to);
}, []);
const bgImage =
banners[currIndex] &&
`${banners[currIndex]["imageUrl"]}?imageView&blur=40x20`;
return (
<WrapperContainer bgImage={bgImage}>
<section className="banner wrap_730_center">
<LeftContainer>
<Carousel
effect="fade"
beforeChange={bannerChange}
ref={bannerRef}
autoplay
>
{banners.map((item, index) => {
return (
<div className="banner-item" key={item.imageUrl}>
<img
className="image"
src={item.imageUrl}
alt={item.typeTitle}
/>
</div>
);
})}
</Carousel>
</LeftContainer>
<RightContainer></RightContainer>
<BannerControl>
<a className="left btn" onClick={(e) => bannerRef.current.prev()}></a>
<button
className="right btn"
onClick={(e) => bannerRef.current.next()}
></button>
</BannerControl>
</section>
</WrapperContainer>
);
})
Example #17
Source File: ImageTable.js From edge-frontend with Apache License 2.0 | 5 votes |
ImageTable = ({ openCreateWizard, openUpdateWizard }) => {
const { count, data, isLoading, hasError } = useSelector(
({ edgeImagesReducer }) => ({
count: edgeImagesReducer?.data?.count || 0,
data: edgeImagesReducer?.data?.data || null,
isLoading:
edgeImagesReducer?.isLoading === undefined
? true
: edgeImagesReducer.isLoading,
hasError: edgeImagesReducer?.hasError,
}),
shallowEqual
);
const isFinalStatus = (status) => status === 'SUCCESS' || status === 'ERROR';
const actionResolver = (rowData) => {
const actionsArray = [];
if (rowData?.isoURL) {
actionsArray.push({
title: (
<Text
className="force-text-black remove-underline"
component="a"
href={rowData.isoURL}
rel="noopener noreferrer"
target="_blank"
>
Download
</Text>
),
});
}
if (isFinalStatus(rowData.imageStatus)) {
actionsArray.push({
title: 'Update Image',
onClick: (_event, _rowId, rowData) => {
openUpdateWizard(rowData.id);
},
});
} else if (rowData?.id) {
actionsArray.push({
title: '',
});
}
return actionsArray;
};
const areActionsDisabled = (rowData) => !isFinalStatus(rowData.imageStatus);
return (
<GeneralTable
apiFilterSort={true}
filters={defaultFilters}
loadTableData={loadEdgeImages}
tableData={{ count, data, isLoading, hasError }}
columnNames={columnNames}
rows={data ? createRows(data) : []}
emptyStateMessage="No images found"
emptyStateActionMessage="Create new image"
emptyStateAction={openCreateWizard}
actionResolver={actionResolver}
areActionsDisabled={areActionsDisabled}
defaultSort={{ index: 4, direction: 'desc' }}
toolbarButtons={[
{
title: 'Create new image',
click: () => openCreateWizard(),
},
]}
/>
);
}
Example #18
Source File: my-profile.js From what-front with MIT License | 5 votes |
MyProfile = () => {
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
const history = useHistory();
return (
<div className={styles.wrapper}>
<div className="container card shadow col-10">
<div className="container pb-2">
<h3 className="pt-3">My Profile</h3>
<hr />
<div className="row mt-3">
<div className="col-sm-4 font-weight-bold pb-1">First Name:</div>
<div className="col-sm-8">{currentUser?.first_name}</div>
</div>
<hr />
<div className="row mt-3">
<div className="col-sm-4 font-weight-bold pb-1">Last Name:</div>
<div className="col-sm-8">{currentUser?.last_name}</div>
</div>
<hr />
<div className="row mt-3">
<div className="col-sm-4 font-weight-bold pb-1">Email address:</div>
<div className="col-sm-8">{currentUser?.email}</div>
</div>
<hr />
<div className="row mb-2">
<div className="col-sm-6 offset-sm-6 d-flex justify-content-end">
<Button
className={styles.button}
onClick={() => (history.push(paths.CHANGE_PASSWORD))}
>
<span>Change password</span>
</Button>
</div>
</div>
</div>
</div>
</div>
);
}
Example #19
Source File: HouseRulesContainer.js From airdnd-frontend with MIT License | 5 votes |
HouseRulesContainer = () => {
const {
id,
address,
checkinTime,
checkoutTime,
rules,
checkin,
checkout,
adult,
child,
infant,
} = useSelector(
state => ({
id: state.home.homeState.home.id,
address: state.home.homeState.home.address,
checkinTime: state.home.homeState.home.checkin,
checkoutTime: state.home.homeState.home.checkout,
rules: state.home.homeState.home.notice.rules,
checkin: state.reservation.checkin,
checkout: state.reservation.checkout,
adult: state.reservation.guests.adult,
child: state.reservation.guests.child,
infant: state.reservation.guests.infant,
}),
shallowEqual,
);
const history = useHistory();
const [readMore, setReadMore] = useState(false);
const shortAddress = address.split(',')[0];
const onReadMore = () => setReadMore(!readMore);
const onNextPage = () =>
history.push(
`/Reservation/GuestInfo/${id}?${checkin && 'checkIn=' + checkin}${
checkout && '&checkOut=' + checkout
}${+adult ? '&adult=' + adult : ''}${+child ? '&child=' + child : ''}${
+infant ? '&infant=' + infant : ''
}`,
);
return (
<HouseRules
address={shortAddress}
checkinTime={checkinTime}
checkoutTime={checkoutTime}
rules={rules}
readMore={readMore}
onReadMore={onReadMore}
onNextPage={onNextPage}
checkin={checkin}
checkout={checkout}
/>
);
}
Example #20
Source File: index.jsx From react-firebase-admin with MIT License | 4 votes |
UserForm = ({ isEditing, isProfile, user, onSubmitHandler, schema }) => {
const { loading, success } = useSelector(
(state) => ({
loading: state.users.loading,
success: state.users.success,
}),
shallowEqual
);
const dispatch = useDispatch();
const { register, handleSubmit, errors, control, watch, setValue } = useForm({
defaultValues: { ...user },
resolver: yupResolver(schema),
});
useEffect(() => {
if (success) {
setValue('file', null);
}
return () => dispatch(usersCleanUp());
}, [dispatch, success, setValue]);
const invalidEmailMessage = useFormatMessage('UserForm.invalidEmail');
const imagePreviewUrl =
watch('file') && watch('file')[0]
? URL.createObjectURL(watch('file')[0])
: user.logoUrl;
const goBackMessage = useFormatMessage('UserForm.goBack');
const pickAnotherFileMessage = useFormatMessage('UserForm.pickAnotherFile');
const pickFileMessage = useFormatMessage('UserForm.pickFile');
const emailMessage = useFormatMessage('UserForm.email');
const adminMessage = useFormatMessage('UserForm.admin');
return (
<>
<div className="tile is-ancestor">
<div className="tile is-parent">
<div className="card tile is-child">
<header className="card-header">
<p className="card-header-title">
<span className="icon">
<i className="mdi mdi-account-edit default" />
</span>
{useFormatMessage('UserForm.userInfo')}
</p>
</header>
<div className="card-content">
<form onSubmit={handleSubmit(onSubmitHandler)}>
{isEditing ? (
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">{emailMessage}</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
type="text"
readOnly="readOnly"
className="input is-static"
name="email"
ref={register}
/>
</div>
</div>
</div>
</div>
) : (
<>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">{emailMessage}</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
className={classNames(`input`, {
'is-danger': errors.email,
})}
ref={register}
name="email"
/>
</div>
</div>
</div>
</div>
{errors.email && (
<div className="field is-horizontal">
<div className="field-label is-normal" />
<div className="field-body">
<ErrorMessage text={invalidEmailMessage} />
</div>
</div>
)}
</>
)}
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage('UserForm.name')}
</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
name="name"
id="name"
className={classNames('input', {
'is-danger': errors.name,
})}
ref={register}
type="text"
/>
</div>
</div>
</div>
</div>
{errors.name && (
<div className="field is-horizontal">
<div className="field-label is-normal" />
<div className="field-body">
<ErrorMessage />
</div>
</div>
)}
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage('UserForm.location')}
</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
className="input"
type="text"
ref={register}
name="location"
/>
</div>
</div>
</div>
</div>
{!isProfile && (
<div className="field has-check is-horizontal">
<div className="field-label">
<label className="label">{adminMessage}</label>
</div>
<div className="field-body">
<div className="field">
<div className="field">
<div className="control">
<label className="b-checkbox checkbox">
<input
type="checkbox"
name="isAdmin"
ref={register}
/>
<span className="check is-primary" />
</label>
</div>
</div>
</div>
</div>
</div>
)}
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage('UserForm.created')}
</label>
</div>
<div className="field-body">
<div className="field">
<Controller
control={control}
name="createdAt"
render={({ onChange, name, value }) => (
<DatePicker
name={name}
onChange={onChange}
date={new Date(value)}
/>
)}
/>
</div>
</div>
</div>
<hr />
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage('UserForm.logo')}
</label>
</div>
<div className="field-body">
<div className="field">
<div className="file has-name">
<label className="file-label">
<input
className="file-input"
type="file"
name="file"
ref={register}
accept="image/*"
/>
<span className="file-cta">
<span className="file-icon">
<i className="mdi mdi-upload" />
</span>
<span className="file-label">
{watch('file') && watch('file').file
? pickAnotherFileMessage
: pickFileMessage}
</span>
</span>
<span className="file-name">
{watch('file') && watch('file')[0]?.name}
</span>
</label>
</div>
</div>
</div>
</div>
<hr />
<div className="field is-horizontal">
<div className="field-label" />
<div className="field-body">
<div className="field">
<div className="field is-grouped">
<div className="control">
<button
type="submit"
className={`button is-primary ${
loading && 'is-loading'
}`}
>
<span>{useFormatMessage('UserForm.submit')}</span>
</button>
</div>
{!isProfile && (
<Link to={paths.USERS} className="button">
{goBackMessage}
</Link>
)}
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div className="tile is-parent preview">
<div className="card tile is-child">
<header className="card-header">
<p className="card-header-title">
<span className="icon">
<i className="mdi mdi-account default" />
</span>
{useFormatMessage('UserForm.userPreview')}
</p>
</header>
<div className="card-content">
{imagePreviewUrl && (
<>
<div className="is-user-avatar image has-max-width is-aligned-center">
<img
className="user-avatar"
src={imagePreviewUrl}
alt="User profile logo preview"
/>
</div>
<hr />
</>
)}
{!isEditing && (
<div className="field">
<label className="label">{emailMessage}</label>
<div className="control is-clearfix">
<input
data-testid="email"
type="text"
readOnly="readOnly"
className="input is-static"
value={watch('email')}
/>
</div>
</div>
)}
<div className="field">
<label className="label">
{useFormatMessage('UserForm.name')}
</label>
<div className="control is-clearfix">
<input
data-testid="name"
type="text"
readOnly="readOnly"
className="input is-static"
value={watch('name')}
/>
</div>
</div>
<div className="field">
<label className="label">
{useFormatMessage('UserForm.location')}
</label>
<div className="control is-clearfix">
<input
data-testid="location"
type="text"
readOnly="readOnly"
className="input is-static"
value={watch('location')}
/>
</div>
</div>
{!isProfile && (
<div className="field">
<label className="label">{adminMessage}</label>
<div className="control is-clearfix" data-testid="admin">
{watch('isAdmin') ? (
<span className="icon">
<i className="mdi mdi-check" />
</span>
) : (
<span className="icon">
<i className="mdi mdi-close" />
</span>
)}
</div>
</div>
)}
<div className="field">
<label className="label">
{useFormatMessage('UserForm.created')}
</label>
<div className="control is-clearfix" data-testid="date">
<p className="date">
{useFormatDate(watch('createdAt'), {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
})}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
}
Example #21
Source File: schedule.js From what-front with MIT License | 4 votes |
Schedule = () => {
const [
dispatchFetchSchedules,
dispatchFetchGroups,
] = useActions([fetchSchedules, globalLoadStudentGroups]);
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
const {
data: schedules,
isLoading: areSchedulesLoading,
isLoaded: areSchedulesLoaded,
} = useSelector(schedulesSelector, shallowEqual);
const {
data: groups,
isLoading: areGroupsLoading,
isLoaded: areGroupsLoaded,
} = useSelector(loadStudentGroupsSelector, shallowEqual);
const [currentWeek, setCurrentWeek] = useState([]);
const [chosenDate, setChosenDate] = useState(new Date());
const history = useHistory();
const DAY_IN_MILLIS = 86400000;
const WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
useEffect(() => {
dispatchFetchSchedules();
dispatchFetchGroups();
}, [dispatchFetchSchedules, dispatchFetchGroups]);
useEffect(() => {
const addZero = (num) => (num < 10 ? `0${num}` : num);
if (areSchedulesLoaded && areGroupsLoaded) {
const weekDayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const firstDayOfWeek = new Date(chosenDate.getTime()
- (chosenDate.getDay() - 1) * DAY_IN_MILLIS);
const weekDays = weekDayNames.map((day, index) => {
const currentDay = new Date(firstDayOfWeek.getTime() + index * DAY_IN_MILLIS);
const lessons = schedules
.filter((schedule) => schedule.dayNumber === index || schedule.repeatRate === 1)
.sort((lesson, nextLesson) => (nextLesson.lessonStart < lesson.lessonStart ? 1 : -1));
return {
id: index,
day,
lessons,
date: `${addZero(currentDay.getDate())}.${addZero(currentDay.getMonth() + 1)}`,
isToday: new Date().toDateString() === currentDay.toDateString(),
isPast: new Date(new Date().toDateString()).getTime() > currentDay.getTime(),
};
});
setCurrentWeek(weekDays);
}
}, [areGroupsLoaded, areSchedulesLoaded, chosenDate, schedules, setCurrentWeek]);
const handleNextWeek = () => {
setChosenDate(new Date(chosenDate.getTime() + WEEK_IN_MILLIS));
};
const handlePreviousWeek = () => {
setChosenDate(new Date(chosenDate.getTime() - WEEK_IN_MILLIS));
};
const handleInputDate = (event) => {
setChosenDate(new Date(event.target.value));
};
const handleEditSchedule = (id) => {
history.push(`${paths.SCHEDULE_EDIT}/${id}`);
};
const handleAddSchedule = () => {
history.push(paths.SCHEDULE_ADD);
};
return (
<div className="container">
<WithLoading isLoading={areGroupsLoading || areSchedulesLoading} className="d-block mx-auto">
<div className="row mb-4">
<div className="col-3 d-flex justify-content-start pl-0">
<input
type="date"
min={`${new Date().getFullYear() - 1}-${new Date().getMonth() + 1}-${new Date().getDate()}`}
max={`${new Date().getFullYear() + 1}-${new Date().getMonth() + 1}-${new Date().getDate()}`}
onChange={handleInputDate}
className={styles['date-input']}
/>
<Button
className="ml-2"
variant="warning"
onClick={() => setChosenDate(new Date())}
>
Today
</Button>
</div>
<div className="col-6 d-flex justify-content-center">
<button
type="button"
className={styles['change-week-btn']}
onClick={handlePreviousWeek}
>
<Icon icon="Arrow" className={classNames(styles.arrow, styles['arrow-left'])} />
</button>
<h4 className="mb-0">{currentWeek[0]?.date} - {currentWeek[currentWeek.length - 1]?.date}</h4>
<button
type="button"
className={styles['change-week-btn']}
onClick={handleNextWeek}
>
<Icon icon="Arrow" className={styles.arrow} />
</button>
</div>
{[3, 4].includes(currentUser.role) ? (
<div className="col-3 d-flex justify-content-end pr-0">
<Button variant="warning" onClick={handleAddSchedule}>
<Icon icon="Plus" className="icon" />
Add schedule
</Button>
</div>
) : null}
</div>
<section className={classNames('row', 'justify-content-center')}>
{ currentWeek.map(({ id, isToday, day, date, lessons, isPast }) => (
<div key={id} className={classNames('col', 'px-0', { [styles['current-day']]: isToday }, styles['day-column'])}>
<hgroup className={styles['day-column-header']}>
<h5 className="text-center">{day}</h5>
<h5 className="text-center">{date}</h5>
</hgroup>
<ul className={styles['lessons-list']}>
{ lessons.map(({ id: lessonId, studentGroupId, lessonEnd, lessonStart }) => (
<li key={lessonId} className={styles['lessons-list__item']}>
<p className={styles['lessons-list__group-name']}>
{groups.find((group) => studentGroupId === group.id).name}
</p>
<div className={styles['lessons-list__details']}>
<Badge
variant={classNames(
{ primary: isToday },
{ secondary: isPast && !isToday },
{ warning: !isToday && !isPast },
)}
>
{lessonStart.substring(0, 5)} - {lessonEnd.substring(0, 5)}
</Badge>
{[3, 4].includes(currentUser.role) ? (
<button
type="button"
className={styles['edit-button']}
onClick={() => handleEditSchedule(lessonId)}
>
<Icon icon="Edit" viewBox="0 0 45 45" size={20} />
</button>
) : null}
</div>
</li>
)) }
</ul>
</div>
)) }
</section>
</WithLoading>
</div>
);
}
Example #22
Source File: index.jsx From react-firebase-admin with MIT License | 4 votes |
Login = () => {
const { error, isAuth, loading, locale } = useSelector(
(state) => ({
error: state.auth.error,
isAuth: !!state.auth.userData.id,
loading: state.auth.loading,
locale: state.preferences.locale,
}),
shallowEqual
);
const dispatch = useDispatch();
const { register, handleSubmit, errors, watch } = useForm({
defaultValues: {},
resolver: yupResolver(schema),
});
useEffect(() => {
document.documentElement.classList.remove(
'has-aside-left',
'has-navbar-fixed-top'
);
return () => {
document.documentElement.classList.add(
'has-aside-left',
'has-navbar-fixed-top'
);
dispatch(authCleanUp());
};
}, [dispatch]);
const isEmailLink = firebase
.auth()
.isSignInWithEmailLink(window.location.href);
const onSubmitHandler = ({ email, password }) => {
if (isEmailLink) {
dispatch(setPassword(email, password, window.location.href));
} else {
dispatch(auth(email, password));
}
};
const onSignInSuccessHandler = (authResult) => {
dispatch(authWithSocialMedia(authResult));
};
const onSignInFailHandler = (signInEror) => {
const signInErrorMessage = firebaseError(signInEror.code, locale);
toastr.error('', signInErrorMessage);
};
const isValidPassword = watch('password') && watch('password').length >= 6;
const invalidEmailMessage = useFormatMessage('Login.invalidEmail');
const safePasswordMessage = useFormatMessage('Login.safePassword');
const unsafePasswordMessage = useFormatMessage('Login.unsafePassword');
const redirect = isAuth && <Redirect to={paths.ROOT} />;
const setNewPasswordMessage = useFormatMessage('Login.setNewPassword');
const loginMessage = useFormatMessage('Login.login');
const setPasswordMessage = useFormatMessage('Login.setPassword');
const forgotPasswordMessage = useFormatMessage('Login.forgotPassword');
const invalidPasswordMessage = useFormatMessage('Login.invalidPassword');
return (
<section className="section hero is-fullheight is-error-section">
{redirect}
<div className="hero-body">
<div className="container">
<div className="columns is-centered">
<div className="column is-two-fifths">
<div className="card has-card-header-background">
<header className="card-header">
<p className="card-header-title">
<span className="icon">
<i className="mdi mdi-lock default" />
</span>
<span>
{isEmailLink ? setNewPasswordMessage : loginMessage}
</span>
</p>
</header>
<div className="card-content">
<form onSubmit={handleSubmit(onSubmitHandler)}>
<div className="field">
<p className="label">{useFormatMessage('Login.email')}</p>
<div className="control is-clearfix">
<input
className={classNames('input', {
'is-danger': errors.email,
})}
name="email"
ref={register}
/>
</div>
{errors.email && (
<ErrorMessage text={invalidEmailMessage} />
)}
</div>
<div className="field">
<p className="label">
{useFormatMessage('Login.password')}
</p>
<div className="control is-clearfix">
<input
className={classNames(
'input',
{
'is-danger': errors.password,
},
{
'is-success': isEmailLink && isValidPassword,
}
)}
type="password"
name="password"
ref={register}
/>
</div>
{errors.password ? (
<ErrorMessage
text={
isEmailLink
? unsafePasswordMessage
: invalidPasswordMessage
}
/>
) : (
isEmailLink &&
isValidPassword && (
<p className="is-success">{safePasswordMessage}</p>
)
)}
</div>
<br />
<div className="field is-grouped">
<div className="control">
<button
type="submit"
className={classNames('button', 'is-black', {
'is-loading': loading,
})}
>
{isEmailLink ? setPasswordMessage : loginMessage}
</button>
</div>
{!isEmailLink && (
<div className="control">
<Link
to={paths.RESET_PASSWORD}
className="button is-outlined"
>
{forgotPasswordMessage}
</Link>
</div>
)}
</div>
{error && (
<p
className={classNames(
'has-text-danger',
classes.errorMessage
)}
>
{error}
</p>
)}
</form>
{!isEmailLink && (
<>
<hr />
<div
className={classNames(
'field',
'is-grouped',
classes.socialButtons
)}
>
<StyledFirebaseAuth
uiConfig={uiConfig(
onSignInSuccessHandler,
onSignInFailHandler
)}
firebaseAuth={firebase.auth()}
/>
</div>
</>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
Example #23
Source File: list-of-students.js From what-front with MIT License | 4 votes |
ListOfStudents = () => {
const {
data: activeStudents,
isLoading: areActiveStudentsLoading,
error: activeStudentsError,
} = useSelector(activeStudentsSelector, shallowEqual);
const {
data: allStudents,
isLoading: areAllStudentsLoading,
error: allStudentsError,
} = useSelector(studentsSelector, shallowEqual);
const { currentUser } = useSelector(currentUserSelector, shallowEqual);
const [dispatchLoadStudents, dispatchLoadActiveStudents, dispatchAddAlert] =
useActions([loadStudents, loadActiveStudents, addAlert]);
const history = useHistory();
const [students, setStudents] = useState([]);
const [visibleStudents, setVisibleStudents] = useState([]);
const INITIAL_CATEGORIES = [
{ id: 0, name: 'firstName', sortedByAscending: false, tableHead: 'Name' },
{ id: 1, name: 'lastName', sortedByAscending: false, tableHead: 'Surname' },
{ id: 2, name: 'email', sortedByAscending: false, tableHead: 'Email' },
];
const [sortingCategories, setSortingCategories] =
useState(INITIAL_CATEGORIES);
const [isShowDisabled, setIsShowDisabled] = useState(false);
const [searchFieldValue, setSearchFieldValue] = useState('');
const [showBlocks, setShowBlocks] = useState(false);
const getDisabledStudents = () => {
const activeStudentIds = activeStudents.map(({ id }) => id);
return allStudents.filter(({ id }) => !activeStudentIds.includes(id));
};
const searchStudents = (searchedStudents, value) =>
searchedStudents.filter(({ firstName, lastName }) =>
`${firstName} ${lastName}`.toLowerCase().includes(value.toLowerCase())
);
const changeActiveCategory = (categories, activeCategoryName) =>
categories.map((category) => {
if (category.name === activeCategoryName) {
return { ...category, sortedByAscending: !category.sortedByAscending };
}
return { ...category, sortedByAscending: false };
});
const downloadStudents = () => {
history.push(paths.STUDENTS_BY_GROUP_ID);
};
useEffect(() => {
dispatchLoadActiveStudents();
}, [dispatchLoadActiveStudents]);
useEffect(() => {
if (isShowDisabled && allStudents.length && !areAllStudentsLoading) {
const disabledStudents = getDisabledStudents();
setStudents(
disabledStudents.map((student, index) => ({ index, ...student }))
);
}
if (!isShowDisabled && activeStudents.length && !areActiveStudentsLoading) {
setStudents(
activeStudents.map((student, index) => ({ index, ...student }))
);
}
setSortingCategories(INITIAL_CATEGORIES);
}, [
activeStudents,
areActiveStudentsLoading,
allStudents,
areAllStudentsLoading,
isShowDisabled,
]);
useEffect(() => {
if (allStudentsError || activeStudentsError) {
dispatchAddAlert(allStudentsError || activeStudentsError);
}
}, [activeStudentsError, allStudentsError, dispatchAddAlert]);
useEffect(() => {
if (isShowDisabled) {
const disabledStudents = getDisabledStudents();
const searchedStudents = searchStudents(
disabledStudents,
searchFieldValue
);
setStudents(
searchedStudents.map((student, index) => ({ index, ...student }))
);
} else {
const searchedStudents = searchStudents(activeStudents, searchFieldValue);
setStudents(
searchedStudents.map((student, index) => ({ index, ...student }))
);
}
}, [searchFieldValue, isShowDisabled]);
const handleSortByParam = (data, categoryParams) => {
const sortedStudents = data;
setSortingCategories(
changeActiveCategory(sortingCategories, categoryParams.sortingParam)
);
setStudents(sortedStudents);
};
const handleShowDisabled = (event) => {
setIsShowDisabled(!isShowDisabled);
if (event.target.checked) {
dispatchLoadStudents();
} else {
dispatchLoadActiveStudents();
}
};
const handleEdit = (event, id) => {
event.stopPropagation();
history.push(`${paths.STUDENT_EDIT}/${id}`);
};
const handleDetails = (id) => {
history.push(`${paths.STUDENTS_DETAILS}/${id}`);
};
const handleSearch = (value) => {
setSearchFieldValue(value);
};
const handleAddStudent = () => {
history.push(paths.UNASSIGNED_USERS);
};
const listProps = {
data: visibleStudents,
handleDetails,
handleEdit,
errors: [
{
message: 'Loading has been failed',
check: [!!allStudentsError, !!activeStudentsError],
},
{
message: 'Student is not found',
check: [!visibleStudents.length && !!searchFieldValue],
},
],
access: true,
fieldsToShow: ['firstName', 'lastName', 'email', 'edit'],
};
return (
<div className="container pt-5">
<div className="row justify-content-between align-items-center mb-3">
<h2 className="col-6">Students</h2>
<div className="col-2 text-right">
{!areActiveStudentsLoading &&
!areAllStudentsLoading &&
`${visibleStudents.length} of ${students.length} students`}
</div>
</div>
<div className="row mr-0">
<div className="col-12 card shadow p-3 mb-5 bg-white ml-2 mr-2">
<div className="row align-items-center d-flex justify-content-between mt-2 mb-3">
<div className="col-2">
<div className="btn-group">
<button
type="button"
className="btn btn-secondary"
disabled={!showBlocks}
onClick={() => setShowBlocks(false)}
>
<Icon icon="List" color="#2E3440" size={25} />
</button>
<button
type="button"
className="btn btn-secondary"
disabled={showBlocks}
onClick={() => setShowBlocks(true)}
>
<Icon icon="Card" color="#2E3440" size={25} />
</button>
</div>
</div>
<div className="col-3">
<Search
value={searchFieldValue}
onSearch={handleSearch}
placeholder="student's name"
/>
</div>
<div className="col-2 custom-control custom-switch text-right">
<input
value={isShowDisabled}
type="checkbox"
className={classNames(
'custom-control-input',
styles['custom-control-input']
)}
id="show-disabled-check"
onChange={handleShowDisabled}
/>
<label
className={classNames(
'custom-control-label',
styles['custom-control-label']
)}
htmlFor="show-disabled-check"
>
Disabled students
</label>
</div>
{[8, 4].includes(currentUser.role) && (
<div className={classNames('col-4 text-right', styles['buttons-block'])}>
<Button
onClick={downloadStudents}
type="button"
className={classNames(
'btn btn-warning ',
styles['left-add-btn']
)}
>
Upload student('s)
</Button>
<Button onClick={handleAddStudent}>
<span>Add a student</span>
</Button>
</div>
)}
</div>
<WithLoading
isLoading={areActiveStudentsLoading || areAllStudentsLoading}
className="d-block mx-auto my-2"
>
{showBlocks ? (
<div className="container d-flex flex-wrap">
<List listType={'block'} props={listProps} />
</div>
) : (
<Table
sortingCategories={sortingCategories}
currentUser={currentUser}
onClick={handleSortByParam}
data={students}
access={{ unruledUser: [2], unassigned: '' }}
>
<List listType="list" props={listProps} />
</Table>
)}
</WithLoading>
</div>
<div
className={classNames(
'row justify-content-between align-items-center mb-3',
styles.paginate
)}
>
<Pagination
items={students}
setVisibleItems={setVisibleStudents}
/>
</div>
</div>
</div>
);
}
Example #24
Source File: index.jsx From react-firebase-admin with MIT License | 4 votes |
ChangePasswordCard = () => {
const { loading, changedPassword } = useSelector(
(state) => ({
loading: state.auth.loading,
changedPassword: state.auth.changedPassword,
}),
shallowEqual
);
const dispatch = useDispatch();
const { register, handleSubmit, watch, setValue, errors } = useForm({
mode: 'onChange',
defaultValues: {
current: '',
new: '',
confirmation: '',
},
resolver: yupResolver(schema),
});
useEffect(() => {
if (changedPassword) {
setValue('current', '');
setValue('new', '');
setValue('confirmation', '');
}
return () => dispatch(authCleanUp());
}, [dispatch, changedPassword, setValue]);
const newPassword = watch('new');
const confirmationPassword = watch('confirmation');
const invalidPasswordMessage = useFormatMessage(
`ChangePassword.invalidPassword`
);
const safePasswordMessage = useFormatMessage(`ChangePassword.safePassword`);
const insecurePasswordMessage = useFormatMessage(
`ChangePassword.insecurePassword`
);
const passwordsMatchMessagge = useFormatMessage(
`ChangePassword.matchPassword`
);
const notMatchPasswordMessage = useFormatMessage(
`ChangePassword.notMatchPassword`
);
const samePasswordMessage = useFormatMessage(`ChangePassword.samePassword`);
const onSubmitHandler = ({ current, confirmation }) => {
dispatch(changeUserPassword(current, confirmation));
};
return (
<div className="card">
<header className="card-header">
<p className="card-header-title">
<span className="icon">
<i className="fa fa-lock" />
</span>
{useFormatMessage(`ChangePassword.changePassword`)}
</p>
</header>
<div className="card-content">
<form onSubmit={handleSubmit(onSubmitHandler)}>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage(`ChangePassword.currentPassword`)}
</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
data-testid="current"
className={classNames('input', {
'is-danger': errors.current,
})}
type="password"
name="current"
ref={register}
/>
</div>
{errors.current && (
<ErrorMessage text={invalidPasswordMessage} />
)}
</div>
</div>
</div>
<hr />
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage(`ChangePassword.newPassword`)}
</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
data-testid="new"
className={classNames(
`input`,
{ 'is-success': newPassword && !errors.new },
{ 'is-danger': errors.new }
)}
type="password"
name="new"
ref={register}
/>
</div>
{errors.new ? (
<ErrorMessage
text={
newPassword.length < 6
? insecurePasswordMessage
: samePasswordMessage
}
/>
) : (
newPassword && (
<p className="is-success">{safePasswordMessage}</p>
)
)}
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">
{useFormatMessage(`ChangePassword.confirmPassword`)}
</label>
</div>
<div className="field-body">
<div className="field">
<div className="control">
<input
data-testid="confirmation"
className={classNames(
`input`,
{
'is-success':
confirmationPassword && !errors.confirmation,
},
{
'is-danger': errors.confirmation,
}
)}
type="password"
name="confirmation"
ref={register}
/>
</div>
{errors.confirmation ? (
<ErrorMessage text={notMatchPasswordMessage} />
) : (
confirmationPassword && (
<p className="is-success">{passwordsMatchMessagge}</p>
)
)}
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label is-normal" />
<div className="field-body">
<div className="field">
<div className="control">
<button
type="submit"
className={`button is-primary ${loading && 'is-loading'}`}
>
{useFormatMessage(`ChangePassword.submits`)}
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
);
}
Example #25
Source File: mentor-details.js From what-front with MIT License | 4 votes |
MentorDetails = ({ id }) => {
const history = useHistory();
const {
data: mentor,
isLoading: mentorIsLoading,
isLoaded: mentorIsLoaded,
error: mentorError
} = useSelector(mentorIdSelector, shallowEqual);
const {
data: mentorGroups,
isLoading: mentorGroupsAreLoading,
isLoaded: mentorGroupsAreLoaded,
error: mentorGroupsError
} = useSelector(mentorGroupsSelector, shallowEqual);
const {
data: mentorCourses,
isLoading:mentorCoursesAreLoading ,
isLoaded: mentorCoursesAreLoaded,
error: mentorCoursesError
} = useSelector(mentorCoursesSelector, shallowEqual);
const [
dispatchLoadMentors,
] = useActions([fetchMentorById, fetchActiveMentors]);
useEffect(() => {
if (mentorError && mentorCoursesError && mentorGroupsError) {
history.push(paths.NOT_FOUND);
}
}, [mentorError, mentorCoursesError, mentorGroupsError, history]);
useEffect(() => {
dispatchLoadMentors(id);
}, [dispatchLoadMentors, id]);
return (
<div className="container" data-testid='mentorDetails'>
<div className="row justify-content-center">
<div className="col-sm-12 card shadow">
<div className="px-2 py-4">
<h3>Mentor Details</h3>
<hr />
{/* test */}
<WithLoading isLoading={mentorIsLoading || !mentorIsLoaded}
className="d-block mx-auto m-0"
>
<div className="row">
<span className="col-12 col-md-6 font-weight-bolder">First Name:</span>
<span className="col-12 col-md-6" data-testid='firstName'>{mentor?.firstName}</span>
</div>
<hr />
<div className="row">
<span className="col-12 col-md-6 font-weight-bolder">Last Name:</span>
<span className="col-12 col-md-6" data-testid='lastName'>{mentor?.lastName}</span>
</div>
<hr />
<div className="row">
<span className="col-12 col-md-6 font-weight-bolder">Email:</span>
<span className="col-12 col-md-6" data-testid='email'>{mentor?.email}</span>
</div>
<hr />
<div className="row">
<div className="col-12 col-md-6 font-weight-bolder"><span>Group('s): </span></div>
{/* test */}
<WithLoading
isLoading={mentorGroupsAreLoading || !mentorGroupsAreLoaded}
className="d-block mx-auto m-0"
>
<div className="col-12 col-md-6 d-flex flex-wrap lead">
{mentorGroups
.map(({ id, name }) => (
<div className="pr-2" key={id}>
<Badge pill variant="info">
<Link
to={`${paths.GROUPS_DETAILS}/${id}`}
className="text-decoration-none text-light group-link"
data-testid='groupLink'
data-testgroupidparam={id}
>{name}
</Link>
</Badge>
</div>
))}
</div>
</WithLoading>
</div>
<hr/>
<div className="row">
<div className="col-12 col-md-6 font-weight-bolder"><span>Course('s): </span></div>
{/* test */}
<WithLoading
isLoading={mentorCoursesAreLoading || !mentorCoursesAreLoaded}
className="d-block mx-auto m-0"
>
<div className="col-12 col-md-6 d-flex flex-wrap lead">
{mentorCourses
.map(({ id, name }) => (
<div className="pr-2" key={id}>
<Badge pill variant="info">
<Link
to={`${paths.COURSE_DETAILS}/${id}`}
className="text-decoration-none text-light course-link"
data-testid='courseLink'
data-testcourseidparam={id}
>{name}</Link>
</Badge>
</div>
))}
</div>
</WithLoading>
</div>
</WithLoading>
</div>
</div>
</div>
</div>
);
}
Example #26
Source File: index.jsx From react-firebase-admin with MIT License | 4 votes |
NavBar = ({ handleMobileToggle, asideMobileActive }) => {
const [navMobileActive, setNavMobileActive] = useState(false);
const { userName, logoUrl, locale } = useSelector(
(state) => ({
userName: state.auth.userData.name,
logoUrl: state.auth.userData.logoUrl,
locale: state.preferences.locale,
}),
shallowEqual
);
const dispatch = useDispatch();
const onClickLogoutHandler = () => {
dispatch(logout());
};
const onMobileToggleHandler = useCallback(() => {
setNavMobileActive(!navMobileActive);
}, [setNavMobileActive, navMobileActive]);
const changeLocaleHandler = (local) => {
dispatch(setUserLocale(local));
};
const locales = availableLocales.filter((local) => local !== locale);
return (
<nav id="navbar-main" className="navbar is-fixed-top">
<div className="navbar-brand">
<a
className="navbar-item is-hidden-desktop jb-aside-mobile-toggle"
onClick={handleMobileToggle}
>
<span className="icon">
<i
className={classNames(
'mdi',
'mdi-24px',
{ 'mdi-forwardburger': !asideMobileActive },
{ 'mdi-backburger': asideMobileActive }
)}
/>
</span>
</a>
</div>
<div className="navbar-brand is-right">
<a
className="navbar-item is-hidden-desktop jb-navbar-menu-toggle"
data-target="navbar-menu"
onClick={onMobileToggleHandler}
>
<span className="icon">
<i
className={classNames(
'mdi',
{ 'mdi-dots-vertical': !navMobileActive },
{ 'mdi-close': navMobileActive }
)}
/>
</span>
</a>
</div>
<div
className={classNames('navbar-menu', 'fadeIn', 'animated', 'faster', {
'is-active': navMobileActive,
})}
id="navbar-menu"
>
<div className="navbar-end">
<div className="navbar-item has-dropdown has-dropdown-with-icons has-divider has-user-avatar is-hoverable">
<a className="navbar-link is-arrowless">
<div className="is-user-avatar">
<span>
<img id={locale} src={flags[locale]} alt={`${locale} flag`} />
</span>
</div>
<span className="icon">
<i className="mdi mdi-chevron-down" />
</span>
</a>
<div className="navbar-dropdown">
{locales.map((local) => (
<a
onClick={() => changeLocaleHandler(local)}
className="navbar-item"
id={local}
key={local}
>
<div className="is-user-avatar">
<span>
<img src={flags[local]} alt={`${local} flag`} />
</span>
</div>
</a>
))}
</div>
</div>
<div className="navbar-item has-dropdown has-dropdown-with-icons has-divider has-user-avatar is-hoverable">
<a className="navbar-link is-arrowless">
<div className="is-user-avatar">
<img src={logoUrl || defaultLogo} alt="User profile" />
</div>
<div className="is-user-name">
<span>{userName}</span>
</div>
<span className="icon">
<i className="mdi mdi-chevron-down" />
</span>
</a>
<div className="navbar-dropdown">
<Link to={paths.PROFILE} onClick={onMobileToggleHandler}>
<span className="icon">
<i className="mdi mdi-account" />
</span>
<span>{useFormatMessage('NavBar.profile')}</span>
</Link>
<hr className="navbar-divider" />
<a
className="navbar-item"
id="logout"
onClick={onClickLogoutHandler}
>
<span className="icon">
<i className="mdi mdi-logout" />
</span>
<span>{useFormatMessage('NavBar.logOut')}</span>
</a>
</div>
</div>
</div>
</div>
</nav>
);
}
Example #27
Source File: index.jsx From NeteaseCloudMusic with MIT License | 4 votes |
Recommend = memo(() => {
const [tabsState, setTabsState] = useState([
{
name: "华语",
},
{
name: "流行",
},
{
name: "摇滚",
},
{
name: "民谣",
},
{
name: "电子",
},
]);
const dispatch = useDispatch();
const { listData } = useSelector(
(state) => ({
listData: state.getIn(["recommendReducer", "personalizedList"]),
}),
shallowEqual
);
useEffect(() => {
dispatch(fetchPersonalizedAsyncAction());
}, [dispatch]);
return (
<>
<BannerComp />
<section className="recommend_container wrap_980_center">
<section className="container_left">
<NRcmdTitleComp title="热门推荐" tabs={tabsState} />
<section className="lists">
{listData.map((item) => {
return <CommonItem key={item.id} itemData={item} />;
})}
</section>
<NRcmdTitleComp title="新碟上架" tag="new" />
<NewDis />
<NRcmdTitleComp title="榜单" tag="new" />
{/* 榜单的主要内容 */}
<section className="n-bilst">
<dl className="blk">
<dt className="top">
<div className="cover">
<img
src="http://p3.music.126.net/DrRIg6CrgDfVLEph9SNh7w==/18696095720518497.jpg?param=100y100"
data-src="http://p3.music.126.net/DrRIg6CrgDfVLEph9SNh7w==/18696095720518497.jpg?param=100y100"
alt=""
className="j-img"
/>
<a
href="/discover/toplist?id=19723756"
className="msk"
title="云音乐飙升榜"
></a>
</div>
<div className="tit">
<a href="/discover/toplist?id=19723756" title="云音乐飙升榜">
<h3 className="fs1">云音乐飙升榜</h3>
</a>
<div className="btn">
<a href="" className="play ">
播放
</a>
<a href="" className="favorites">
收藏
</a>
</div>
</div>
</dt>
<dd className="list-container">
<ol className="">
<li>
<span className="no no-top">1</span>
<a href="" className="nm">
是想你的声音啊
</a>
<div className="oper">
<a href="" className="btn play"></a>
<a href="" className="btn add"></a>
<a href="" className="btn shou"></a>
</div>
</li>
</ol>
</dd>
</dl>
<dl className="blk">
<dt className="top">
<div className="cover">
<img
src="http://p3.music.126.net/N2HO5xfYEqyQ8q6oxCw8IQ==/18713687906568048.jpg?param=100y100"
data-src="http://p3.music.126.net/DrRIg6CrgDfVLEph9SNh7w==/18696095720518497.jpg?param=100y100"
alt=""
className="j-img"
/>
<a
href="/discover/toplist?id=19723756"
className="msk"
title="云音乐飙升榜"
></a>
</div>
<div className="tit">
<a href="/discover/toplist?id=19723756" title="云音乐飙升榜">
<h3 className="fs1">云音乐新歌榜</h3>
</a>
<div className="btn">
<a href="" className="play ">
播放
</a>
<a href="" className="favorites">
收藏
</a>
</div>
</div>
</dt>
<dd className="list-container">
<ol className="">
<li>
<span className="no no-top">1</span>
<a href="" className="nm">
是想你的声音啊
</a>
<div className="oper">
<a href="" className="btn play"></a>
<a href="" className="btn add"></a>
<a href="" className="btn shou"></a>
</div>
</li>
</ol>
</dd>
</dl>
<dl className="blk blk1">
<dt className="top">
<div className="cover">
<img
src="http://p3.music.126.net/sBzD11nforcuh1jdLSgX7g==/18740076185638788.jpg?param=100y100"
data-src="http://p3.music.126.net/DrRIg6CrgDfVLEph9SNh7w==/18696095720518497.jpg?param=100y100"
alt=""
className="j-img"
/>
<a
href="/discover/toplist?id=19723756"
className="msk"
title="云音乐飙升榜"
></a>
</div>
<div className="tit">
<a href="/discover/toplist?id=19723756" title="云音乐飙升榜">
<h3 className="fs1">网易原创歌曲榜</h3>
</a>
<div className="btn">
<a href="" className="play ">
播放
</a>
<a href="" className="favorites">
收藏
</a>
</div>
</div>
</dt>
<dd className="list-container">
<ol className="">
<li>
<span className="no no-top">1</span>
<a href="" className="nm">
是想你的声音啊
</a>
<div className="oper">
<a href="" className="btn play"></a>
<a href="" className="btn add"></a>
<a href="" className="btn shou"></a>
</div>
</li>
</ol>
</dd>
</dl>
</section>
</section>
<section className="container_right">
<UserLoginComp />
</section>
</section>
</>
);
})
Example #28
Source File: TreeMenu.js From ra-treemenu with MIT License | 4 votes |
Menu = (props) => {
const {
className,
dense,
hasDashboard,
onMenuClick,
logout,
dashboardlabel,
resources,
...rest
} = props;
const classes = useStyles(props);
const translate = useTranslate();
const open = useSelector((state) => state.admin.ui.sidebarOpen);
const pathname = useSelector((state) => state.router.location.pathname);
const resources = resources || useSelector(getResources, shallowEqual);
const hasList = (resource) => (resource.hasList);
const handleToggle = (parent) => {
/**
* Handles toggling of parents dropdowns
* for resource visibility
*/
setState(state => ({ [parent]: !state[parent] }));
};
const isXSmall = useMediaQuery((theme) =>
/**
* This function is not directly used anywhere
* but is required to fix the following error:
*
* Error: Rendered fewer hooks than expected.
* This may be caused by an accidental early
* return statement.
*
* thrown by RA at the time of rendering.
*/
theme.breakpoints.down('xs')
);
const isParent = (resource) => (
/**
* Check if the given resource is a parent
* i.e. dummy resource for menu parenting
*/
resource.options &&
resource.options.hasOwnProperty('isMenuParent') &&
resource.options.isMenuParent
);
const isOrphan = (resource) => (
/**
* Check if the given resource is an orphan
* i.e. has no parents defined. Needed as
* these resources are supposed to be rendered
* as is
*
*/
resource.options &&
!resource.options.hasOwnProperty('menuParent') &&
!resource.options.hasOwnProperty('isMenuParent')
);
const isChildOfParent = (resource, parentResource) => (
/**
* Returns true if the given resource is the
* mapped child of the parentResource
*/
resource.options &&
resource.options.hasOwnProperty('menuParent') &&
resource.options.menuParent == parentResource.name
);
const geResourceName = (slug) => {
if (!slug) return;
var words = slug.toString().split('_');
for (var i = 0; i < words.length; i++) {
var word = words[i];
words[i] = word.charAt(0).toUpperCase() + word.slice(1);
}
return words.join(' ');
}
const getPrimaryTextForResource = (resource) => {
let resourcename = '';
if (resource.options && resource.options.label)
resourcename = resource.options.label;
else if (resource.name) {
resourcename = translate(`resources.${resource.name}.name`);
if (resourcename.startsWith('resources.'))
resourcename = geResourceName(resource.name);
}
return resourcename;
}
const MenuItem = (resource) => (
/**
* Created and returns the MenuItemLink object component
* for a given resource.
*/
<MenuItemLink
key={resource.name}
to={`/${resource.name}`}
primaryText={getPrimaryTextForResource(resource)}
leftIcon={
resource.icon
? <resource.icon />
: <DefaultIcon />
}
onClick={onMenuClick}
dense={dense}
sidebarIsOpen={open}
/>
);
/**
* Mapping a "parent" entry and then all its children to the "tree" layout
*/
const mapParentStack = (parentResource) => (
<CustomMenuItem
key={parentResource.name}
handleToggle={() => handleToggle(parentResource.name)}
isOpen={state[parentResource.name] || parentActiveResName === parentResource.name}
sidebarIsOpen={open}
name={getPrimaryTextForResource(parentResource)}
icon={parentResource.icon ? <parentResource.icon /> : <LabelIcon />}
dense={dense}
>
{
// eslint-disable-next-line
resources
.filter((resource) => isChildOfParent(resource, parentResource) && hasList(resource))
.map((childResource) => { return MenuItem(childResource); })
}
</CustomMenuItem>
);
/**
* Mapping independent (without a parent) entries
*/
const mapIndependent = (independentResource) => hasList(independentResource) && MenuItem(independentResource);
/**
* Initialising the initialExpansionState and
* active parent resource name at the time of
* initial menu rendering.
*/
const initialExpansionState = {};
let parentActiveResName = null;
/**
* Initialise all parents to inactive first.
* Also find the active resource name.
*/
resources.forEach(resource => {
if (isParent(resource)) {
initialExpansionState[resource.name] = false;
} else if (pathname.startsWith(`/${resource.name}`) && resource.options.hasOwnProperty('menuParent')) {
parentActiveResName = resource.options.menuParent;
}
});
const [state, setState] = useState(initialExpansionState);
/**
* The final array which will hold the array
* of resources to be rendered
*/
const resRenderGroup = [];
/**
* Looping over all resources and pushing the menu tree
* for rendering in the order we find them declared in
*/
resources.forEach(r => {
if (isParent(r)) resRenderGroup.push(mapParentStack(r));
if (isOrphan(r)) resRenderGroup.push(mapIndependent(r));
});
return (
<div>
<div
className={classnames(classes.main, className, {
[classes.open]: open,
[classes.closed]: !open,
})}
{...rest}
>
{hasDashboard && (
<DashboardMenuItem
onClick={onMenuClick}
dense={dense}
sidebarIsOpen={open}
primaryText={dashboardlabel}
/>
)}
{resRenderGroup}
</div>
</div>
);
}
Example #29
Source File: edit-group.js From what-front with MIT License | 4 votes |
EditGroup = ({
id: groupId, studentGroupData, studentsData, mentorsData, coursesData,
}) => {
const {
isLoading: isEditing,
isLoaded: isEdited,
error: editingError,
} = useSelector(editStudentGroupSelector, shallowEqual);
const [dispatchEditGroup, dispatchAddAlert] = useActions([editStudentGroup, addAlert]);
const history = useHistory();
const {
data: group,
isLoading: isGroupLoading,
isLoaded: isGroupLoaded,
} = studentGroupData;
const {
data: students,
isLoading: areStudentsLoading,
isLoaded: areStudentsLoaded,
} = studentsData;
const {
data: mentors,
isLoading: areMentorsLoading,
isLoaded: areMentorsLoaded,
} = mentorsData;
const {
data: courses,
isLoading: areCoursesLoading,
loaded: areCoursesLoaded,
} = coursesData;
const [groupMentors, setGroupMentors] = useState([]);
const [mentorInputError, setMentorInputError] = useState('');
const [groupStudents, setGroupStudents] = useState([]);
const [studentInputError, setStudentInputError] = useState('');
const prevGroupMentors = usePrevious(groupMentors);
const prevGroupStudents = usePrevious(groupStudents);
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
useEffect(() => {
if (!isEditing && isEdited && !editingError) {
history.push(paths.GROUPS);
dispatchAddAlert('The group has been successfully edited!', 'success');
}
if (!isEditing && !isEdited && editingError) {
dispatchAddAlert(editingError);
}
}, [isEdited, editingError, isEditing, history, dispatchAddAlert]);
useEffect(() => {
if (mentors.length) {
setGroupMentors(mentors.filter(({ id }) => group.mentorIds?.includes(id)));
}
}, [group.mentorIds, mentors]);
useEffect(() => {
if (students.length) {
setGroupStudents(students.filter(({ id }) => group.studentIds?.includes(id)));
}
}, [group.studentIds, students]);
const addMentor = (mentorFullInfo, clearField) => {
const [selectedMentorEmail] = mentorFullInfo.split(' ').reverse();
const mentor = mentors.find(({ email }) => email === selectedMentorEmail);
if (mentor) {
clearField();
setMentorInputError('');
setGroupMentors((prevState) => [...prevState, mentor]);
} else {
setMentorInputError('Mentor not found');
}
};
const addStudent = (studentFullInfo, clearField) => {
const [selectedStudentEmail] = studentFullInfo.split(' ').reverse();
const student = students.find(({ email }) => email === selectedStudentEmail);
if (student) {
clearField();
setStudentInputError('');
setGroupStudents((prevState) => [...prevState, student]);
} else {
setStudentInputError('Student not found');
}
};
const removeMentor = useCallback((mentorId) => {
setGroupMentors(groupMentors.filter(({ id }) => id !== mentorId));
}, [groupMentors]);
const removeStudent = useCallback((studentId) => {
setGroupStudents(groupStudents.filter(({ id }) => id !== studentId));
}, [groupStudents]);
const handleSubmit = ({
name, startDate, finishDate, courseId,
}) => {
const newGroupData = {
id: groupId,
name,
courseId,
startDate: commonHelpers.transformDateTime({ isDayTime: false, dateTime: startDate }).formInitialValue,
finishDate: commonHelpers.transformDateTime({ isDayTime: false, dateTime: finishDate }).formInitialValue,
studentIds: [...new Set(groupStudents.map(({ id }) => id))],
mentorIds: [...new Set(groupMentors.map(({ id }) => id))],
};
dispatchEditGroup(newGroupData);
};
const handleReset = () => {
setGroupStudents(students.filter(({ id }) => group.studentIds.includes(id)));
setGroupMentors(mentors.filter(({ id }) => group.mentorIds.includes(id)));
setStudentInputError('');
setMentorInputError('');
};
return (
<div className="w-100">
<div className="justify-content-center">
<div className="w-100 card shadow p-4">
<WithLoading
isLoading={isGroupLoading ||
areMentorsLoading ||
areCoursesLoading ||
areStudentsLoading}
className={styles['loader-centered']}
>
<Formik
initialValues={{
name: group.name,
startDate: commonHelpers.transformDateTime({ isDayTime: false, dateTime: group.startDate }).formInitialValue,
finishDate: commonHelpers.transformDateTime({ isDayTime: false, dateTime: group.finishDate }).formInitialValue,
courseId: group.courseId,
mentor: '',
student: '',
}}
onSubmit={handleSubmit}
validationSchema={editGroupValidation}
validateOnMount={false}
>
{({ values, errors, setFieldValue, isValid, dirty }) => (
<Form className="px-2 py-4" name="start-group" data-testid="editGroup">
<h3>Group Editing</h3>
<hr />
<div className="row mb-3 align-items-start">
<div className="col d-flex align-items-center">
<label className="mt-2" htmlFor="name">Group name:
</label>
</div>
<div className="col-md-8">
<Field
className={classNames('form-control', { 'border-danger': errors.name })}
type="text"
name="name"
id="group-name"
placeholder="group name"
/>
{errors.name && <p className="w-100 text-danger mb-0">{errors.name}</p>}
</div>
</div>
<div className="row mb-3">
<div className="col d-flex align-items-center">
<label className="mb-0" htmlFor="course">Course:</label>
</div>
<div className="col-md-8">
<Field
id="course-name"
as="select"
className={classNames('custom-select')}
name="courseId"
>
<option value={group.courseId} key={group.courseId}>
{ courses.find((course) => course.id === group.courseId)?.name }
</option>
{
courses
.filter((course) => course.id !== group.courseId)
.map((course) => (
<option value={course.id} key={course.id}>{course.name}</option>
))
}
</Field>
</div>
</div>
<div className="row mb-3">
<div className="col d-flex align-items-center">
<label className="mb-0" htmlFor="start-date">Start date:</label>
</div>
<div className="col-md-8">
<Field
className={classNames('form-control', { 'border-danger': errors.startDate })}
type="date"
name="startDate"
id="start-date"
/>
{errors.startDate && <p className="text-danger mb-0">{errors.startDate}</p>}
</div>
</div>
<div className="row mb-3">
<div className="col d-flex align-items-start">
<label className="mt-2" htmlFor="finish-date">Finish date:</label>
</div>
<div className="col-md-8">
<Field
className={classNames('form-control', { 'border-danger': errors.finishDate })}
type="date"
name="finishDate"
id="finish-date"
/>
{errors.finishDate && <p className="text-danger mb-0">{errors.finishDate}</p>}
</div>
</div>
<div className="row mb-3">
<div className="col d-flex align-items-start">
<label className="mt-2" htmlFor="mentor">Mentors:</label>
</div>
<div className="col-md-8">
<div className="d-flex" data-testid="mentor-field-wrapper">
<Field
className="form-control f"
type="text"
name="mentor"
list="mentors-list"
/>
<datalist id="mentors-list">
{
mentors
.filter(({ id }) => !groupMentors.find((mentor) => mentor.id === id))
.map(({
id, firstName, lastName, email,
}) => <option key={id} value={`${firstName} ${lastName} ${email}`} />)
}
</datalist>
<Button
id="add-mentor-btn"
variant="info"
onClick={() => addMentor(values.mentor, () => setFieldValue('mentor', ''))}
disabled={!dirty}
>
+
</Button>
</div>
{mentorInputError && <p className="text-danger mb-0">{mentorInputError}</p>}
<div className="w-100">
<ul className="col-md-12 d-flex flex-wrap justify-content-between p-0">
{
groupMentors.map(({ id, firstName, lastName }) => (
<li
key={id}
id="chosenMentorName"
className={classNames(
'd-flex bg-light border border-outline-secondary rounded',
styles['datalist-item'],
)}
>
{firstName} {lastName}
<button
type="button"
className={classNames('btn p-0 ml-auto mr-2 font-weight-bold', styles.cross)}
onClick={() => removeMentor(id)}
>
X
</button>
</li>
))
}
</ul>
</div>
</div>
</div>
<div className="row mb-3">
<div className="col d-flex align-items-start">
<label className="mt-2" htmlFor="finish-date">Students:</label>
</div>
<div className="col-md-8">
<div className="d-flex" data-testid="students-field-wrapper">
<Field
className="form-control f"
type="text"
name="student"
list="students-list"
/>
<datalist id="students-list">
{
students
.filter(({ id }) => !groupStudents.find((mentor) => mentor.id === id))
.map(({
id, firstName, lastName, email,
}) => <option key={id} value={`${firstName} ${lastName} ${email}`} />)
}
</datalist>
<Button
id="add-student-btn"
variant="info"
onClick={() => addStudent(values.student, () => setFieldValue('student', ''))}
disabled={!dirty}
>
+
</Button>
</div>
{studentInputError && <p className="text-danger mb-0">{studentInputError}</p>}
<div className="w-100">
<ul className="col-12 d-flex flex-wrap justify-content-between p-0">
{
groupStudents.map(({ id, firstName, lastName }) => (
<li
key={id}
className={classNames(
'd-flex bg-light border border-outline-secondary rounded',
styles['datalist-item'],
)}
>
{firstName} {lastName}
<button
type="button"
className={classNames('btn p-0 ml-auto mr-2 font-weight-bold', styles.cross)}
onClick={() => removeStudent(id)}
>
X
</button>
</li>
))
}
</ul>
</div>
</div>
</div>
<div className="row justify-content-around mt-4">
<Button type="reset" variant="secondary" className={classNames('w-25', styles['clear-button'])} disabled={ (!dirty && prevGroupMentors !== groupMentors && prevGroupStudents !== groupStudents) || isEditing} onClick={handleReset}>
Reset
</Button>
<Button id="submit" type="submit" className="btn btn-secondary w-25 buttonConfirm" disabled={!isValid || (!dirty && prevGroupMentors !== groupMentors && prevGroupStudents !== groupStudents) || isEditing}>
Confirm
</Button>
</div>
</Form>
)}
</Formik>
</WithLoading>
</div>
</div>
</div>
);
}