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

Feature/solved issues #153

Merged
merged 85 commits into from
Apr 21, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
8dc53be
refactor loadResponses
Mar 25, 2020
19b8605
add tab bar, show solved issues
Mar 25, 2020
b9fd9c3
add more dashboard translations
Mar 25, 2020
59f17cd
refactor component, add heroFound button
Mar 25, 2020
6618dc4
add react-web-tabs
Mar 25, 2020
660afa2
add tertiary
Mar 25, 2020
7f4d56e
import react-web-tabs.css
Mar 25, 2020
4cb8452
add classes to overwrite default tab bar
Mar 25, 2020
7667da7
refactor deletion and solved handling, rework responses button, layou…
Mar 25, 2020
da910bb
remove showResponses, add message
Mar 25, 2020
d97f116
add reactjs-popup
Mar 27, 2020
97c8055
add min-w-90
Mar 27, 2020
96dff67
add popup translation keys
Mar 27, 2020
1b1b5d2
add popup helper
Mar 27, 2020
cc5f77f
add HintsPopup component
Mar 27, 2020
8fbcf07
Merge branch 'develop' into feature/solved-issues
Mar 27, 2020
b2a35cf
fix handleSolved
Mar 27, 2020
bcf81de
Merge branch 'master' of github.com:kenodressel/quarantine-hero into …
Mar 27, 2020
6c823d9
move loadResponses to /services
Mar 28, 2020
045de92
add showAsSolved
Mar 28, 2020
0dc3635
fix collectionName
Mar 28, 2020
d024911
remove invalid property
Mar 28, 2020
1b32e5e
consistent naming
Mar 28, 2020
a51c51c
only fetch responses on click
Mar 28, 2020
890f176
use useCollectionDataOnce for data fetching
Mar 28, 2020
cfec9ba
add translations for poup content
Mar 28, 2020
c7b7959
update translation keys, remove ternary operator
Mar 28, 2020
3b18e47
fix popup content
Mar 28, 2020
6c8a61a
refactor Popup component
Mar 29, 2020
6508e0d
fix condition
Mar 29, 2020
c8d3ccc
fix style for mobile
Mar 29, 2020
69c9f6f
Merge branch 'master' into feature/solved-issues
Mar 29, 2020
b6fac07
add solveReassure translations
Mar 30, 2020
fed15b4
add attemptingToSolve, styling fixes
Mar 30, 2020
207f298
fix tabs for mobile
Mar 30, 2020
2b3c618
add userIsOnMobile helper
Mar 30, 2020
84c837c
add max-width-5
Mar 30, 2020
83d6464
add PopupContentSolveReassure, refactoring
Mar 30, 2020
553aa79
fix margin-top
Mar 30, 2020
7248137
fix icon margin-left
Mar 30, 2020
f6c5680
use QuestionMarkSvg
Mar 30, 2020
330dcc4
Merge branch 'master' into feature/solved-issues
Mar 31, 2020
a7c2418
add initial dashboard spec
Mar 31, 2020
7e95af0
refactor loginVerifiedUser
Mar 31, 2020
b275611
add cypress tags
Mar 31, 2020
9e6566f
Merge branch 'feature/solved-issues' of github.com:kenodressel/quaran…
Mar 31, 2020
0e0335f
update yarn.lock
Mar 31, 2020
776aaec
Merge branch 'master' of github.com:kenodressel/quarantine-hero into …
Apr 1, 2020
00574cb
add data-cy attributes
Apr 2, 2020
790f7f4
add testing utilities
Apr 3, 2020
2de742d
reset attemptingToDelete on popup close
Apr 3, 2020
5eabb53
add first version of e2e tests
Apr 3, 2020
cbe903b
change questionmark height
Apr 3, 2020
d228d75
fix test cases
Apr 4, 2020
faa35a5
adapt timeouts
Apr 4, 2020
488c371
fix test timeouts
Apr 4, 2020
56a3a76
fix padding, add whitespace-pre-line
Apr 4, 2020
747ed73
add linebreaks
Apr 4, 2020
6e153cd
fix tabs font-size for mobile
Apr 4, 2020
01f0cde
Merge branch 'master' of github.com:kenodressel/quarantine-hero into …
Apr 4, 2020
4394b3d
remove conosle.log
Apr 4, 2020
945634d
add xs breakpoint (iPhone 5/SE)
Apr 7, 2020
07652b0
replace commonButtonClasses with composing utility btn-common
Apr 7, 2020
f7d258f
fix buttons for mobile
Apr 7, 2020
2ac9ac9
skip dashboard tests
Apr 7, 2020
ff1f991
Merge branch 'master' of github.com:kenodressel/quarantine-hero into …
Apr 7, 2020
30591e3
remove unused import
Apr 7, 2020
b184b01
add missing dependency for useEffect
Apr 7, 2020
93320aa
Merge branch 'feature/solved-issues' of github.com:kenodressel/quaran…
Apr 7, 2020
2317b77
Some minor design changes
florianschmidt1994 Apr 8, 2020
7063377
remove react-with-firebase-auth
Apr 11, 2020
5a81650
change to "Helfenden"
Apr 11, 2020
f5435ff
introduce "solved" state, import Svgs as ReactComponent
Apr 11, 2020
71200cc
Merge branch 'master' into feature/solved-issues
Apr 14, 2020
7a97012
update xsMax css class
Apr 14, 2020
4508219
remove xsMax, change xs to 321px
Apr 17, 2020
0dfd191
Merge branch 'master' into feature/solved-issues
Apr 21, 2020
88c6868
adapt pronouns to polite speech
Apr 21, 2020
4131610
fix linter
Apr 21, 2020
c1ead59
refactor parseDoc
Apr 21, 2020
8783be5
more polite speech fixes
Apr 21, 2020
53eb627
fix className
Apr 21, 2020
c6b5171
fix showAsSolved
Apr 21, 2020
c2109da
fix responses text style
Apr 21, 2020
e5ccce7
Fixed styling
mauriceackel Apr 21, 2020
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 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"react-scroll-up-button": "^1.6.4",
"react-share": "^4.1.0",
"react-slider": "^1.0.3",
"react-web-tabs": "^1.0.1",
florianschmidt1994 marked this conversation as resolved.
Show resolved Hide resolved
"react-with-firebase-auth": "^1.3.0",
mauriceackel marked this conversation as resolved.
Show resolved Hide resolved
"reactjs-popup": "^1.5.0",
"tailwindcss": "^1.2.0"
},
"scripts": {
Expand Down
27 changes: 21 additions & 6 deletions public/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,16 @@
"needsHelp": "Hilfe benötigt",
"yourRequests": "Deine Hilfegesuche",
"noRequests": "Du hast noch keine Hilfegesuche eingestellt. Du kannst ein neues Gesuch",
"noResolvedRequests": "Du hast noch keine Held*innen für deine Gesuche gefunden. Du kannst ein neues Gesuch",
"create": "erstellen",
"yourNotifications": "Deine Benachrichtigungen",
"noNotificationsSubscribed": "Du hast noch keine Benachrichtigungen aktiviert. Du kannst neue Benachrichtigungen",
"here": "hier",
"register": "registrieren"
"register": "registrieren",
"tabs": {
"open": "OFFEN",
"solved": "ABGESCHLOSSEN"
}
},
"privacy": {
"title": "Datenschutzerklärung"
Expand Down Expand Up @@ -190,12 +195,22 @@
"somebodyAt": "Jemand in",
"needsHelp": "braucht Hilfe!",
"before": "vor",
"showResponses": "Antwort ansehen",
"showResponses_plural": "{{count}} Antworten ansehen",
"hideResponses": "Antwort verbergen",
"hideResponses_plural": "{{count}} Antworten verbergen",
"message": "{{count}} Nachricht",
"message_plural": "{{count}} Nachrichten",
"deleteRequestForHelp": "Löschen",
"registrationReason": "den Beitrag zu melden"
"registrationReason": "den Beitrag zu melden",
"heroFound": "Held*in gefunden",
"popup": {
"heroFound": "Ich habe eine*n Held*in gefunden",
"deleteAnyway": "Trotzdem löschen",
"wasYourRequestSuccessful": "War dein Hilfegesuch erfolgreich?",
"yourRequestWasDeleted": "Dein Hilfegesuch wurde gelöscht!",
"backToOverview": "Zurück zur Übersicht",
"reassureDeletion": "Diese Anfrage wirklich löschen?",
"cancel": "Abbrechen",
"deleteTerminally": "Endgültig löschen",
"createNewRequest": "Neue Anfrage Erstellen"
}
},
"filteredList": {
"in": "In",
Expand Down
195 changes: 158 additions & 37 deletions src/components/Entry.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import formatDistance from 'date-fns/formatDistance';
import { de } from 'date-fns/locale';
import { useTranslation } from 'react-i18next';
import { useAuthState } from 'react-firebase-hooks/auth';
import Popup from 'reactjs-popup';

import DeleteOutlineIcon from '@material-ui/icons/DeleteOutline';
import ExpandLessIcon from '@material-ui/icons/ExpandLess';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import MailOutlineIcon from '@material-ui/icons/MailOutline';
import DoneIcon from '@material-ui/icons/Done';
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';

import fb from '../firebase';
import Responses from './Responses';

import { getPopupContentComponent, getButtonForPopup } from './Popup';

export default function Entry(props) {
const {
showFullText = false,
showAsSolved = false,
location = '',
id = '',
request = '',
Expand All @@ -31,13 +36,16 @@ export default function Entry(props) {
const date = formatDistance(new Date(timestamp), Date.now(), { locale: de }); // @TODO get locale from i18n.language or use i18n for formatting
const [responsesVisible, setResponsesVisible] = useState(false);

const [deleted, setDeleted] = useState('');
const [deleted, setDeleted] = useState(false);
const [attemptingToDelete, setAttemptingToDelete] = useState(false);
const [popupVisible, setPopupVisible] = useState(false);
const [attemptingToReport, setAttemptingToReport] = useState(false);

const userIsLoggedIn = !!user && !!user.uid;
const userLoggedInAndReportedEntryBefore = userIsLoggedIn && reportedBy.includes(user.uid);
const [reported, setReported] = useState(userLoggedInAndReportedEntryBefore);
const entryBelongsToCurrentUser = userIsLoggedIn && user.uid === uid;
const collectionName = !showAsSolved ? 'ask-for-help' : 'solved-posts';

let textToDisplay;
if (showFullText) {
Expand All @@ -50,21 +58,53 @@ export default function Entry(props) {

const handleDelete = async (e) => {
e.preventDefault();
const doc = await fb.store.collection('/ask-for-help').doc(props.id).get();
await fb.store.collection('/deleted').add({
askForHelpId: doc.id, ...doc.data(),
const doc = await fb.store.collection(collectionName).doc(props.id).get();
await fb.store.collection('/deleted').doc(props.id).set({
collectionName, ...doc.data(),
});
fb.store.collection('/ask-for-help').doc(props.id).delete();
mauriceackel marked this conversation as resolved.
Show resolved Hide resolved
setDeleted(true);
setAttemptingToDelete(false);
setPopupVisible(true);
};

const handleSolved = async (e) => {
e.preventDefault();
const askForHelpDoc = await fb.store.collection('ask-for-help').doc(props.id).get();
const data = askForHelpDoc.data();
await fb.store.collection('solved-posts').doc(props.id).set(data);
setDeleted(true);
setAttemptingToDelete(false);
};

const handleNewAskForHelp = async (e) => {
e.preventDefault();
setPopupVisible(false);
return history.push('/ask-for-help');
};

const initializeDelete = async (e) => {
e.preventDefault();
setAttemptingToDelete(true);
setPopupVisible(true);
};

const cancelDelete = async (e) => {
e.preventDefault();
setAttemptingToDelete(false);
setPopupVisible(false);
};

const backToOverview = async (e) => {
e.preventDefault();
setPopupVisible(false);
};

const reportEntry = async (e) => {
// prevents redirect to the parent component, as this is clicked on a button within a Link component
// https://stackoverflow.com/a/53005834/8547462
e.preventDefault();

const collectionName = 'reported-posts';
const reportedPostsCollection = fb.store.collection(collectionName);
const reportedPostsCollection = fb.store.collection('reported-posts');

// redirect the user to the login page, as we can only store user ids for logged-in users
if (!userIsLoggedIn) {
Expand Down Expand Up @@ -95,10 +135,100 @@ export default function Entry(props) {
numberOfResponsesText = `${responses} ${t('components.entry.repliesReceived')}`;
}

const commonButtonClasses = 'px-6 py-3 uppercase font-open-sans font-bold text-center';
const positiveActionButtonClasses = `bg-secondary text-white hover:opacity-75 rounded mb-2 block min-w-90 ${commonButtonClasses}`;
const deleteButtonClasses = `bg-red-200 text-primary ${commonButtonClasses}`;
const invertedDeleteButtonClasses = `text-primary font-medium min-w-90 ${commonButtonClasses.replace('font-bold', '')}`;

const InitializeDeletionButton = getButtonForPopup(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried to make this as generic as possible, to avoid redundant code repetitions, since all of the buttons in the popup are similar. Please let me know if you have a better suggestion for how to do this!

Copy link
Member

Choose a reason for hiding this comment

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

In 'src/styles/index.css' there are some classes that use apply in order to combine styles from different classes into one. Maybe we could use something like this as well here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, however, it is more than just styling since we also specify a different onClick handling and icon for each button..

deleteButtonClasses,
responses === 0 ? t('components.entry.deleteRequestForHelp') : null,
initializeDelete,
<DeleteOutlineIcon className="mb-1" />,
);

const HeroFoundButtonInPopup = getButtonForPopup(
positiveActionButtonClasses,
t('components.entry.popup.heroFound'),
handleSolved,
<DoneIcon className="ml-2 mb-1" />,
showAsSolved,
);

const NewAskForHelpButtonInPopup = getButtonForPopup(
positiveActionButtonClasses,
t('components.entry.popup.createNewRequest'),
handleNewAskForHelp,
<ArrowForwardIosIcon className="ml-2 mb-1" />,
);

const CancelButton = getButtonForPopup(
positiveActionButtonClasses,
t('components.entry.popup.cancel'),
cancelDelete,
null,
);

const DeleteAnywayButtonInPopup = getButtonForPopup(
invertedDeleteButtonClasses,
t('components.entry.popup.deleteAnyway'),
handleDelete,
<ArrowForwardIosIcon className="ml-2 mb-1" />,
);

const DeleteTerminallyButton = getButtonForPopup(
invertedDeleteButtonClasses,
t('components.entry.popup.deleteTerminally'),
handleDelete,
<ArrowForwardIosIcon className="ml-2 mb-1" />,
);

const BackToOverviewButtonInPopup = getButtonForPopup(
invertedDeleteButtonClasses,
t('components.entry.popup.backToOverview'),
backToOverview,
<ArrowForwardIosIcon className="ml-2 mb-1" />,
);

const PopupContentSolvedHint = getPopupContentComponent(
t('components.entry.popup.wasYourRequestSuccessful'),
<HeroFoundButtonInPopup />,
<DeleteAnywayButtonInPopup />,
);

const PopupContentDeleteReassure = getPopupContentComponent(
t('components.entry.popup.reassureDeletion'),
<CancelButton />,
<DeleteTerminallyButton />,
);

const PopupContentDeleteSuccess = getPopupContentComponent(
t('components.entry.popup.yourRequestWasDeleted'),
<NewAskForHelpButtonInPopup />,
<BackToOverviewButtonInPopup />,
);

const HintsPopup = () => (
<Popup
modal
open={popupVisible}
onClose={(e) => {
e.preventDefault();
setPopupVisible(false);
}}
contentStyle={{ width: '30%', padding: '0' }}
florianschmidt1994 marked this conversation as resolved.
Show resolved Hide resolved
>
{/* eslint-disable-next-line no-nested-ternary */}
janikga marked this conversation as resolved.
Show resolved Hide resolved
{attemptingToDelete
? ((responses === 0 || showAsSolved) ? <PopupContentDeleteReassure /> : <PopupContentSolvedHint />)
: (deleted ? <PopupContentDeleteSuccess /> : <></>)}
</Popup>
);

if (deleted) {
return null;
// make popup component available to show the success hint, if the entry was previously deleted
return <HintsPopup />;
}

// eslint-disable-next-line no-nested-ternary
const buttonClass = reported ? 'btn-report-flagged' : (attemptingToReport ? 'btn-report-abort' : 'btn-report-unflagged');

Expand All @@ -114,37 +244,28 @@ export default function Entry(props) {
return <></>;
}

const commonButtonClasses = 'px-6 py-3 uppercase font-open-sans font-bold text-center';
const expandIconProps = {
className: 'ml-2',
style: {
fontSize: '32px', marginTop: '-4px', marginBottom: '-4px', verticalAlign: 'bottom',
},
};
const heroFoundButtonClasses = showAsSolved
? `bg-secondary text-white hover:opacity-75 ${commonButtonClasses}`
: `bg-tertiary text-secondary hover:bg-secondary hover:text-white ${commonButtonClasses}`;

return (
<div className="flex flex-row mt-4 -mb-2 -mx-4 text-sm rounded-b overflow-hidden">
{responses === 0
? <div className={`bg-gray-200 text-gray-700 flex-grow ${commonButtonClasses}`}>{numberOfResponsesText}</div>
: (
<button type="button" className={`bg-secondary hover:opacity-75 text-white flex-grow ${commonButtonClasses}`} onClick={toggleResponsesVisible}>
{responsesVisible ? (
<>
{t('components.entry.hideResponses', { count: responses })}
<ExpandLessIcon {...expandIconProps} />
</>
) : (
<>
{t('components.entry.showResponses', { count: responses })}
<ExpandMoreIcon {...expandIconProps} />
</>
)}
</button>
<>
<button type="button" className={`bg-secondary hover:opacity-75 text-white flex-1 ${commonButtonClasses}`} onClick={toggleResponsesVisible}>
{t('components.entry.message', { count: responses })}
<MailOutlineIcon className="ml-2 mb-1" />
</button>
<button type="button" className={`flex-1 mx-px ${heroFoundButtonClasses}`} onClick={handleSolved} disabled={showAsSolved}>
{t('components.entry.heroFound')}
<DoneIcon className="ml-2 mb-1" />
</button>
</>
)}
<button type="button" className={`bg-red-200 text-primary hover:bg-primary hover:text-white ${commonButtonClasses}`} onClick={handleDelete}>
{t('components.entry.deleteRequestForHelp')}
<DeleteOutlineIcon style={{ fontSize: '20px' }} className="ml-2" />
</button>
<InitializeDeletionButton />
<HintsPopup />
</div>
);
})();
Expand Down Expand Up @@ -211,7 +332,7 @@ export default function Entry(props) {
<>
{requestCard}
{mayDeleteEntryAndSeeResponses
? <div className={responsesVisible ? '' : 'hidden'}><Responses id={id} /></div>
? <div className={responsesVisible ? '' : 'hidden'}><Responses id={id} collectionName={collectionName} /></div>
: <></>}
</>
);
Expand Down
52 changes: 52 additions & 0 deletions src/components/Popup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import i18n from 'i18next';

export function getPopupContentComponent(heading, firstButtonComponent, secondButtonComponent) {
Copy link
Member

Choose a reason for hiding this comment

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

Is there any way we could make a React component out of this instead of a function? I'm thinking maybe something like https://medium.com/@Dane_s/react-js-compound-components-a6e54b5c9992, but I've never tried that myself so not 💯 percent sure

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will check it out!

const popupContentClasses = 'p-4 bg-kaki font-open-sans flex flex-col justify-center items-center';

const textBodyWasYourRequestSuccessful = (
<>
<p className="mt-2">
Lass die Community wissen ob deine Suche erfolgreich war.
florianschmidt1994 marked this conversation as resolved.
Show resolved Hide resolved
<i>#strongertogether</i>
</p>
<p>Anstatt dein Hilfegesuch zu löschen markieren wir es als abgeschlossen.</p>
<p>Du kannst es natürlich jederzeit trotzdem löschen.</p>
</>
);

const textBodyYourRequestWasDeleted = (
<>
<p>Bitte beachte, dass es einen Moment dauern kann, bis der Eintrag gelöscht wird.</p>
<p>
Du kannst natürlich jederzeit eine neue Anfrage erstellen und eine*n Held*in um Hilfe bitten.
<i>#strongertogether</i>
</p>
</>
);

/* eslint-disable-next-line no-nested-ternary */
janikga marked this conversation as resolved.
Show resolved Hide resolved
const textBody = heading === i18n.t('components.entry.popup.wasYourRequestSuccessful')
? textBodyWasYourRequestSuccessful
: (heading === i18n.t('components.entry.popup.yourRequestWasDeleted') ? textBodyYourRequestWasDeleted : null);

return () => (
<div className={popupContentClasses}>
<div className="mb-3 pl-8 pt-2 pb-5 min-w-full">
<div className="font-bold">{heading}</div>
{textBody}
</div>
{firstButtonComponent}
{secondButtonComponent}
</div>
);
}

export function getButtonForPopup(commonButtonClasses, text, onClickFunction, icon, disabled = false) {
return () => (
<button type="button" className={commonButtonClasses} onClick={onClickFunction} disabled={disabled}>
{text}
{icon}
</button>
);
}
Loading