Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LF-4631 (1): Implement sensor addition UI flow #3659

Merged
merged 25 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8f20c32
LF-4631 Add translation for loading screen
SayakaOno Jan 18, 2025
3c3b6a4
LF-4631 Add loading screen
SayakaOno Jan 18, 2025
974e006
LF-4631 Update WithStepperProgressBar to support loading screen
SayakaOno Jan 18, 2025
a7c4f23
LF-4631 Add story for ContextForm with onContinueAction function
SayakaOno Jan 18, 2025
73bebe5
LF-4631 Add HeaderWithBackAndClose component
SayakaOno Jan 20, 2025
f135739
LF-4631 Modify WithStepperProgressBar to add SIMPLE_HEADER variant
SayakaOno Jan 20, 2025
5272a50
LF-4631 Add stories for ContextForm SIMPLE_HEADER variant
SayakaOno Jan 20, 2025
c177765
LF-4631 Adjust HeaderWithBackAndClose styles
SayakaOno Jan 20, 2025
1d59f30
LF-4631 Add missing style for HeaderWithBackAndClose
SayakaOno Jan 20, 2025
b3dbe1b
LF-4631 Add PostSensor container
SayakaOno Jan 21, 2025
367c20d
LF-4631 Add PostSensor container to full width routes
SayakaOno Jan 9, 2025
a13ea33
LF-4631 Add PostSensor route
SayakaOno Jan 9, 2025
269011f
LF-4631 Add translation
SayakaOno Jan 21, 2025
80fbb4c
LF-4631 Add missing parameter to onContinueAction in WithStepperProgr…
SayakaOno Jan 22, 2025
0e815c0
LF-4631 Add background color to the sensor addition form
SayakaOno Jan 22, 2025
390c3cc
LF-4631 Reformat routes file
SayakaOno Jan 27, 2025
ea21e60
LF-4631 Add PostSensorForm route for manager and EO
SayakaOno Jan 27, 2025
d9b34a9
LF-4631 Add back button background color (transparent)
SayakaOno Jan 27, 2025
03346be
LF-4631 Reformat routes file
SayakaOno Jan 27, 2025
b73afaa
LF-4631 Update WithStepperProgressBar for flexible header
SayakaOno Jan 29, 2025
46a2983
LF-4631 Update PostSensor to adjust ContextForm props
SayakaOno Jan 29, 2025
6040723
LF-4631 Remove PostSensor from FULL_WIDTH_ROUTE
SayakaOno Jan 29, 2025
b911f68
LF-4631 Add padding to form wrapper
SayakaOno Jan 29, 2025
9f03152
LF-4631 Update WithStepperProgressBar to handle headerComponent null
SayakaOno Jan 29, 2025
421f31e
LF-4631 Add story for Stepper form without header
SayakaOno Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/webapp/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"EDITING": "Editing...",
"ENTER_VALUE": "Enter value",
"EXPORT": "Export",
"FETCHING_YOUR_DATA": "Sit back, we’re fetching your {{dataName}} data",
"FINISH": "Finish",
"FROM": "from",
"GO_BACK": "Go back",
Expand Down
1 change: 1 addition & 0 deletions packages/webapp/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1782,6 +1782,7 @@
"DAYS_AGO": "{{time}} day(s) ago",
"DEPTH": "Depth",
"DETAIL": {
"ADD_SENSORS": "Add sensors",
"BRAND": "Brand",
"BRAND_TOOLTIP": "Brands that LiteFarm can integrate with are shown below. If you would no longer like to use this sensor brand, try retiring this sensor instead.",
"DEPTH": "Depth",
Expand Down
4 changes: 2 additions & 2 deletions packages/webapp/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import { NotistackSnackbar } from './containers/Snackbar/NotistackSnackbar';
import { OfflineDetector } from './containers/hooks/useOfflineDetector/OfflineDetector';
import styles from './styles.module.scss';
import Routes from './routes';
import { ANIMALS_URL } from './util/siteMapConstants';
import { ANIMALS_URL, MAP_URL } from './util/siteMapConstants';

function App() {
const [isCompactSideMenu, setIsCompactSideMenu] = useState(false);
const [isFeedbackSurveyOpen, setFeedbackSurveyOpen] = useState(false);
const FULL_WIDTH_ROUTES = ['/map', ANIMALS_URL];
const FULL_WIDTH_ROUTES = [MAP_URL, ANIMALS_URL];
const isFullWidth = FULL_WIDTH_ROUTES.some((path) => matchPath(history.location.pathname, path));

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2025 LiteFarm.org
* This file is part of LiteFarm.
*
* LiteFarm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiteFarm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/

import { ReactNode } from 'react';
import Icon from '../../Icons';
import TextButton from '../Button/TextButton';
import { ReactComponent as XIcon } from '../../../assets/images/x-icon.svg';
import styles from './styles.module.scss';

interface HeaderWithBackAndCloseProps {
title: ReactNode;
onCancel?: () => void;
onGoBack?: () => void;
}

const HeaderWithBackAndClose = ({ title, onCancel, onGoBack }: HeaderWithBackAndCloseProps) => {
return (
<div className={styles.headerWrapper}>
<div className={styles.container}>
<div className={styles.leftContainer}>
{onGoBack && (
<TextButton onClick={onGoBack}>
<Icon iconName="CHEVRON_LEFT" className={styles.backButton} />
</TextButton>
)}
<div className={typeof title === 'string' ? styles.textTitle : ''}>{title}</div>
</div>
{onCancel && (
<TextButton className={styles.closeButtonContainer} onClick={onCancel}>
<XIcon className={styles.closeButton} />
</TextButton>
)}
</div>
</div>
);
};

export default HeaderWithBackAndClose;
38 changes: 38 additions & 0 deletions packages/webapp/src/components/Form/ContextForm/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2025 LiteFarm.org
* This file is part of LiteFarm.
*
* LiteFarm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LiteFarm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details, see <https://www.gnu.org/licenses/>.
*/

import { useTranslation } from 'react-i18next';
import Spinner from '../../Spinner';
import styles from './styles.module.scss';

interface LoadingProps {
dataName?: string;
}

const Loading = ({ dataName = '' }: LoadingProps) => {
const { t } = useTranslation(['translation', 'common']);

return (
<div className={styles.loadingScreen}>
<div>
<Spinner />
</div>
<div className={styles.loadingText}>{t('common:LOADING')}</div>
<div className={styles.loadingMessage}>{t('common:FETCHING_YOUR_DATA', { dataName })}</div>
</div>
);
};

export default Loading;
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ import FloatingContainer from '../../FloatingContainer';
import FormNavigationButtons from '../FormNavigationButtons';
import FixedHeaderContainer from '../../Animals/FixedHeaderContainer';
import CancelFlowModal from '../../Modals/CancelFlowModal';
import Loading from './Loading';
import styles from './styles.module.scss';

interface WithStepperProgressBarProps {
children: ReactNode;
history: History;
steps: { formContent: ReactNode; title: string }[];
steps: {
formContent: ReactNode;
title: string;
onContinueAction?: (values: any) => Promise<void>;
dataName?: string;
}[];
activeStepIndex: number;
cancelModalTitle: string;
isCompactSideMenu: boolean;
Expand All @@ -59,6 +65,7 @@ interface WithStepperProgressBarProps {
setIsEditing?: React.Dispatch<React.SetStateAction<boolean>>;
showCancelFlow?: boolean;
setShowCancelFlow?: React.Dispatch<React.SetStateAction<boolean>>;
headerComponent?: ((props: HeaderProps) => JSX.Element) | null;
}

export const WithStepperProgressBar = ({
Expand All @@ -84,12 +91,14 @@ export const WithStepperProgressBar = ({
setIsEditing,
showCancelFlow,
setShowCancelFlow,
headerComponent = StepperProgressBar,
}: WithStepperProgressBarProps) => {
const [transition, setTransition] = useState<{ unblock?: () => void; retry?: () => void }>({
unblock: undefined,
retry: undefined,
});
const [isSaving, setIsSaving] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const isSummaryPage = hasSummaryWithinForm && activeStepIndex === steps.length - 1;
const isSingleStep = steps.length === 1;
Expand All @@ -107,6 +116,13 @@ export const WithStepperProgressBar = ({
return () => unblock();
}, [isSummaryPage, isDirty, history]);

useEffect(() => {
// Reset loading state whenever the step changes
if (isLoading) {
setIsLoading(false);
}
}, [activeStepIndex]);

const isFinalStep =
(!hasSummaryWithinForm && activeStepIndex === steps.length - 1) ||
(hasSummaryWithinForm && activeStepIndex === steps.length - 2);
Expand All @@ -123,6 +139,20 @@ export const WithStepperProgressBar = ({
};

const onContinue = async () => {
const { onContinueAction } = steps[activeStepIndex];

if (onContinueAction) {
setIsLoading(true);
try {
// Execute the custom action for the current step before proceeding to the next one
await onContinueAction(getValues());
} catch (error) {
console.error(error);
setIsLoading(false);
return;
}
}

if (isFinalStep) {
setIsSaving(true);
await handleSubmit((data: FieldValues) => onSave(data, onSuccess, setFormResultData))();
Expand All @@ -149,13 +179,20 @@ export const WithStepperProgressBar = ({
setShowCancelFlow?.(false);
};

if (isLoading) {
return <Loading dataName={steps[activeStepIndex].dataName} />;
}

return (
<StepperProgressBarWrapper
isSingleStep={isSingleStep}
{...stepperProgressBarConfig}
title={stepperProgressBarTitle}
steps={steps.map(({ title }) => title)}
activeStep={activeStepIndex}
onGoBack={onGoBack}
onCancel={onCancel}
headerComponent={headerComponent}
>
<div className={styles.contentWrapper}>{children}</div>
{shouldShowFormNavigationButtons && (
Expand All @@ -181,22 +218,29 @@ export const WithStepperProgressBar = ({
);
};

type StepperProgressBarWrapperProps = StepperProgressBarProps & {
type HeaderProps = StepperProgressBarProps & {
onGoBack: () => void;
onCancel: () => void;
};

type StepperProgressBarWrapperProps = HeaderProps & {
children: ReactNode;
isSingleStep: boolean;
headerComponent: ((props: HeaderProps) => JSX.Element) | null;
};

const StepperProgressBarWrapper = ({
children,
isSingleStep,
headerComponent,
...stepperProgressBarProps
}: StepperProgressBarWrapperProps) => {
if (isSingleStep) {
return <>{children}</>;
}

return (
<FixedHeaderContainer header={<StepperProgressBar {...stepperProgressBarProps} />}>
<FixedHeaderContainer header={headerComponent && headerComponent(stepperProgressBarProps)}>
{children}
</FixedHeaderContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,83 @@
padding-bottom: 104px; // button height 72px + button top 16px + button bottom 16px
}
}

.loadingScreen {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.loadingText {
color: #000;
font-size: 16px;
font-weight: 700;
line-height: 24px;
margin-bottom: 16px;
}

.loadingMessage {
color: #000;
font-size: 16px;
line-height: 24px;
}

/*----------------------------------------
HeaderWithBackAndClose
----------------------------------------*/
.headerWrapper {
width: 100%;
border-bottom: 1px solid var(--Colors-Neutral-Neutral-50);
box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.05);

.container {
max-width: 1024px;
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 16px 8px 12px 8px;
}

.leftContainer {
display: flex;
align-items: center;
gap: 8px;
}

.backButton {
padding: 0;
min-width: 24px;
min-height: 24px;
background-color: transparent;

kathyavini marked this conversation as resolved.
Show resolved Hide resolved
svg {
width: 24px;
height: 24px;
}

path {
stroke: var(--Colors-Neutral-Neutral-600);
stroke-width: 2px;
}
}

.textTitle {
font-size: 16px;
letter-spacing: -0.352px;
}

.closeButtonContainer {
display: flex;
justify-content: center;
align-items: center;
width: 24px;
height: 24px;
}

.closeButton {
width: 12px;
height: 12px;
}
}
Loading
Loading