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

Export fixes #646

Merged
merged 8 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
2,098 changes: 1,149 additions & 949 deletions Gemfile.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from "react";
import * as S from "./ActionButton.style";

interface ButtonProps {
onClick: () => void;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
children: string | JSX.Element | (JSX.Element | string)[];
}

Expand Down
3 changes: 1 addition & 2 deletions app/javascript/react/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import React from "react";
import * as S from "./Button.style";

interface ButtonProps {
onClick: () => void;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
children: string | JSX.Element | (JSX.Element | string)[];
}

const Button = ({ children, ...props }: ButtonProps) => (
<S.Button {...props}>{children}</S.Button>
);


export { Button };
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useState, useRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

empty line between please 😃

ModalContainer,
ModalContent,
ButtonsWrapper,
FlexWrapper,
ActionButton,
CancelButtonX,
} from "./ExportModal.style";
import closeButton from "../../assets/icons/closeButton.svg";

interface ModalProps {
isOpen: boolean;
hasActionButton?: boolean;
handleActionButton?: (event: React.FormEvent) => void;
buttonName?: string;
buttonHasIcon: boolean;
iconName: string;
onClose?: () => void;
position: {
bottom: number;
left: number;
};
children: React.ReactNode;
}

const DesktopExportModal: React.FC<ModalProps> = ({
isOpen,
hasActionButton = true,
handleActionButton,
buttonName,
buttonHasIcon: hasIcon,
iconName,
onClose,
position,
children,
}) => {
const modalRef = useRef<HTMLDialogElement | null>(null);
const buttonRef = useRef<HTMLButtonElement | null>(null);

const handleCloseModal = () => {
onClose?.();
};

const handleKeyDown = (event: React.KeyboardEvent<HTMLDialogElement>) => {
if (event.key === "Escape") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can move this to KeyboarKeys.ts

handleCloseModal();
}
};

useEffect(() => {
const modalElement = modalRef.current;
if (modalElement && isOpen) {
modalElement.showModal();
} else if (modalElement) {
modalElement.close();
}
}, [isOpen]);

const { t } = useTranslation();

return (
<>
<ModalContainer
isOpen={isOpen}
bottom={position.bottom}
left={position.left}
onKeyDown={handleKeyDown}
>
<ModalContent>
<FlexWrapper>
<CancelButtonX onClick={handleCloseModal}>
<img src={closeButton} alt={t("closeWhite.altCloseButton")} />
</CancelButtonX>
</FlexWrapper>
{children}
<ButtonsWrapper>
{hasActionButton && (
<ActionButton onClick={handleActionButton || (() => {})}>
{buttonName || ""}
{hasIcon ? (
<img src={iconName} alt={t(`${iconName}.altResetButton`)} />
) : null}
</ActionButton>
)}
</ButtonsWrapper>
</ModalContent>
</ModalContainer>
</>
);
};

export { DesktopExportModal };
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { useTranslation } from "react-i18next";

import { useAppDispatch } from "../../store/hooks";
import { exportSession } from "../../store/exportSessionSlice";
import { Modal } from "../Modal";
import { DesktopExportModal } from "./DesktopExportModal";
import { EmailInput, RedErrorMessage } from "./EmailInput";
import { ConfirmationMessage } from "./ConfirmationMessage";
import downloadWhite from "../../assets/icons/downloadWhite.svg";
import { Modal } from "../Modal";

export interface ExportModalData {
email: string;
Expand All @@ -19,6 +20,10 @@ const initialExportModalData: ExportModalData = {
interface ExportDataModalProps {
sessionId: string;
isOpen: boolean;
position: {
bottom: number;
left: number;
};
onSubmit: (data: ExportModalData) => void;
onClose: () => void;
}
Expand All @@ -27,6 +32,7 @@ const ExportDataModal: React.FC<ExportDataModalProps> = ({
sessionId,
onSubmit,
isOpen,
position,
onClose,
}) => {
const focusInputRef = useRef<HTMLInputElement | null>(null);
Expand All @@ -37,6 +43,7 @@ const ExportDataModal: React.FC<ExportDataModalProps> = ({
null
);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const isMobile = window.innerWidth <= 768;
smialko marked this conversation as resolved.
Show resolved Hide resolved
const EMAIL_FIELD = "email";
const dispatch = useAppDispatch();
const { t } = useTranslation();
Expand Down Expand Up @@ -90,7 +97,7 @@ const ExportDataModal: React.FC<ExportDataModalProps> = ({
setConfirmationMessage(t("exportDataModal.confirmationMessage"));
};

return (
return isMobile ? (
<Modal
title={t("exportDataModal.title")}
hasCloseButton={!confirmationMessage}
Expand All @@ -117,6 +124,32 @@ const ExportDataModal: React.FC<ExportDataModalProps> = ({
</form>
)}
</Modal>
) : (
<DesktopExportModal
hasActionButton={!confirmationMessage}
buttonName={t("exportDataModal.exportButton")}
buttonHasIcon
iconName={downloadWhite}
handleActionButton={handleSubmit}
isOpen={isOpen}
position={position}
onClose={onClose}
>
{confirmationMessage ? (
<ConfirmationMessage message={confirmationMessage} />
) : (
<form onSubmit={handleSubmit}>
<div>
<EmailInput
focusInputRef={focusInputRef}
value={formState.email}
onChange={handleInputChange}
/>
</div>
{errorMessage && <RedErrorMessage>{errorMessage}</RedErrorMessage>}
</form>
)}
</DesktopExportModal>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import styled from "styled-components";
import { white, blue, gray100 } from "../../assets/styles/colors";
import { Button } from "../Button/Button.style";
import { media } from "../../utils/media";

interface ModalProps {
isOpen: boolean;
bottom: number;
left: number;
onKeyDown: React.KeyboardEventHandler<HTMLDialogElement>;
}

const ModalContainer = styled.div<ModalProps>`
display: ${({ isOpen }) => (isOpen ? "flex" : "none")};
justify-content: center;
align-items: center;
position: absolute;
bottom: ${({ bottom }) => `${bottom}px`};
left: ${({ left }) => `${left}px`};
z-index: 999;
`;

const ModalContent = styled.div`
background-color: ${white};
opacity: 1;
border-radius: 8px;
position: relative;
padding: 1rem;
min-height: 10vh;
min-width: 11vw;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
`;

const ButtonsWrapper = styled.div`
display: flex;
justify-content: center;
padding-top: 0.625rem;
`;

const FlexWrapper = styled.div`
display: flex;
padding-bottom: 0.625rem;
`;

const ActionButton = styled(Button)`
background-color: ${blue};
color: ${white};
font-weight: 100;
border: none;
@media ${media.desktop} {
height: 30px;
font-size: 1rem;
img {
width: 1rem;
height: 1rem;
}
}
`;

const CancelButtonX = styled(Button)`
color: black;
font-weight: 100;
border: none;

@media ${media.desktop} {
display: flex;
align-items: center;
height: auto;
padding: 0;
cursor: pointer;
img {
width: 1rem;
height: 1rem;
}
}
`;

const TextInput = styled.input`
width: 100%;
padding: 0.625rem;
border: 1px solid ${gray100};
border-radius: 4px;
margin-bottom: 0.625rem;
`;

export {
ModalContainer,
ModalContent,
ButtonsWrapper,
FlexWrapper,
ActionButton,
CancelButtonX,
TextInput,
};
2 changes: 1 addition & 1 deletion app/javascript/react/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface ModalProps {
const Modal: React.FC<ModalProps> = ({
isOpen,
title,
hasCloseButton = true,
hasCloseButton = false,
hasActionButton = true,
handleActionButton,
buttonName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ interface Props {

const StationActionButtons = ({ sessionId }: Props) => {
const [isExportModalOpen, setExportModalOpen] = useState<boolean>(false);
const [modalPosition, setModalPosition] = useState<{
bottom: number;
left: number;
}>({ bottom: 0, left: 0 });

const { t } = useTranslation();

Expand Down Expand Up @@ -47,7 +51,18 @@ const StationActionButtons = ({ sessionId }: Props) => {
}
};

const handleOpenExportModal = () => {
const handleOpenDesktopExportModal = (
event: React.MouseEvent<HTMLButtonElement>
) => {
const rect = event.currentTarget.getBoundingClientRect();
setModalPosition({
bottom: window.innerHeight - rect.bottom + rect.height + 1,
left: rect.left,
});
setExportModalOpen(true);
};

const handleOpenMobileExportModal = () => {
setExportModalOpen(true);
};

Expand All @@ -59,7 +74,7 @@ const StationActionButtons = ({ sessionId }: Props) => {
<>
<S.MobileButtons>
<ActionButton
onClick={handleOpenExportModal}
onClick={handleOpenMobileExportModal}
aria-labelledby={t("calendarHeader.altExportSession")}
>
<img src={downloadImage} />
Expand All @@ -80,16 +95,18 @@ const StationActionButtons = ({ sessionId }: Props) => {
<img src={copyLink} alt={t("Copy link")} />
</Button>
<Button
onClick={handleOpenExportModal}
onClick={handleOpenDesktopExportModal}
aria-labelledby={t("calendarHeader.altExportSession")}
>
{t("calendarHeader.exportSession")} <img src={downloadImage} />
</Button>
</S.DesktopButtons>

<ExportDataModal
sessionId={sessionId}
isOpen={isExportModalOpen}
onClose={handleCloseExportModal}
position={modalPosition}
onSubmit={(formData) => {}}
/>
</>
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/react/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"lastUpdate": "Last update",
"updateFrequencyTitle": "Updates every",
"copyLink": "COPY LINK",
"exportSession": "EXPORT SESSION",
"exportSession": "DOWNLOAD DATA",
"altAlert": "Be notified about new measurements",
"altShareLink": "Get link to the session you are viewing",
"altExportSession": "Export the session you are exploring",
Expand All @@ -71,7 +71,7 @@
},
"exportDataModal": {
"title": "Export data to e-mail",
"exportButton": "Export data",
"exportButton": "EMAIL DATA",
"emailPlaceholder": "loremipsum@ac.com",
"confirmationMessage": "Exported sessions will be emailed within minutes. The email may end up in your spam folder.",
"invalidEmailMessage": "Please enter a valid email address."
Expand Down
4 changes: 2 additions & 2 deletions config/application.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require_relative "boot"
require_relative 'boot'

require "rails/all"
require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Expand Down