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

Added store hours and store details #96

Merged
merged 16 commits into from
Apr 18, 2020
Merged

Added store hours and store details #96

merged 16 commits into from
Apr 18, 2020

Conversation

thumn
Copy link
Contributor

@thumn thumn commented Apr 17, 2020

What's new in this PR

  • Store cards now display whether the store is currently open or what time it will next be open
  • The store card now navigates to a separate store card on click that provides information on the store's address, phone number, hours by day of the week, and accepted programs (NOTE: accepted programs is not finished)
  • Detailed store hour schedules bold the current day and hours

Relevant Links

N/A

Online sources

Date

Related PRs

N/A

How to review

Navigate through stores and make sure each store's card can successfully navigate to its corresponding store screen. The current day should be correctly bolded, and the store open status summary should correctly reflect whether the store is open or when it'll next be open. For example, Yang Market has a particular complicated store hours input.

Next steps

Start work on tags for accepted programs in the stores list. This will also be applied to the store screens.

NOTE: I'm getting a "each child in a list should have a unique 'key' prop" error -- does anyone know how to fix this? The file is StoreHours.js. I also can't figure out how to make the <StoreDetailText> primary green on the StoreCard, but it works fine on the store screen. (Might be passing in the wrong prop, but the component seems to take in color as a prop so I'm not too sure.)

Also, I sometimes get a weird "store is undefined" error on MapScreen that goes away if I reload the app. Sadly this might end up circling back to the useFocusEffect hook issue/refactoring task we set aside :(

Tests Performed, Edge Cases

Tested with hard-coded store hour inputs:
9am-8pm Tu-Sat, 9am-7pm Sun, Closed Mon
8am-7pm Daily
Open 24/7
7am-Midnight Daily

Also manually compared hours between Stores table in the Airtable and the ones that showed up in the store screens. I looked up the more edge-case-y stores and compared the info on the store card and corresponding store screen. (e.g. Yang Market, Mellon Market, T & G Grocery, Smiley Mart).

Screenshots

IMG_2708
IMG_2709

CC: @tommypoa @wangannie

@thumn thumn requested review from wangannie and tommypoa April 17, 2020 11:32
@thumn
Copy link
Contributor Author

thumn commented Apr 17, 2020

LOL Just remembered I'm using the wrong-facing phone icon...

Copy link
Member

@wangannie wangannie left a comment

Choose a reason for hiding this comment

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

THIS IS SO GREAT! The comments you left are SUPER helpful for documentation and helping me figure out how it all works. I really appreciate that you took the time to add those!

The Open Now isn't working for me right now...I noted why I think this might be happening but it's very strange. Besides that, I noted a few ways we can maybe accommodate some different input styles/edge cases (better for handoff) + cleanup. Great work Thu 😍 !

@@ -197,7 +191,13 @@ export default class MapScreen extends React.Component {

renderContent = () => {
return (
<BottomSheetContainer>
<TouchableOpacity
Copy link
Member

Choose a reason for hiding this comment

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

Right now the whole map drawer becomes the button -- probably add this touchable opacity to StoreCard instead (so it also works from StoreListScreen.

Copy link
Member

Choose a reason for hiding this comment

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

Also, if TouchableOpacity continues to do this, it might be better to switch to TouchableHighlight or something that doesn't do this when clicked 😂
image

lib/mapUtils.js Outdated
}

const convertedOpeningTime = openingHour * 60 + openingMin;
const convertedClosingTime = closingHour * 60 + closingMin;
Copy link
Member

Choose a reason for hiding this comment

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

This is always showing up like it's currently closed for every store.
image

When I logged the current date & opening & closing times, I got this so I think it's concatenating openingHour*60 with openingMin instead of adding 😂 and same with closing. Wild.
image

Copy link
Member

Choose a reason for hiding this comment

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

When I added parseInt for min, it works well!

Copy link
Member

Choose a reason for hiding this comment

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

Oh wait also it only seems to work for Nam's (the first store that loaded for me bc of the simulator location I set). When I select any other store, it says the convertedOpening and convertedClosing are NaN, even though the todaysStoreHours is correct. Very odd..
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah shoot I forgot the minutes were strings RIP thanks for catching that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh also the issue was that some of the times don't have minutes so I didn't check for minutes before converting something that didn't exist! So that's why it's NaN but that's fixed now tyty

@@ -50,6 +51,7 @@ export default function StoresStackNavigator() {
},
}}
/>
<StoresStack.Screen name="StoreScreen" component={StoreScreen} />
Copy link
Member

Choose a reason for hiding this comment

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

Hm could you rename this to StoreDetailsScreen for consistency with ProductDetailsScreen? StoreScreen could maybe get confused with StoreListScreen.

</NavButton>
<NavTitle>{store.storeName}</NavTitle>
</NavHeaderContainer>
<View style={{ marginLeft: 16, marginTop: 30 }}>
Copy link
Member

Choose a reason for hiding this comment

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

Can you make this a ScrollView? The size is fine not scrolling on a larger phone, but it would get cut off on smaller devices.

<StoreDetailText>{storeHours}</StoreDetailText>
<StoreDetailText
color={
storeOpenStatus === 'Open 24/7' ? Colors.primaryGreen : Colors.black
Copy link
Member

Choose a reason for hiding this comment

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

I think I was also having this issue with the 'Earn and redeem Healthy Rewards here' line, so I added a prop greenText to StoreDetailText (see line 114), so you can probably just move this logic into something like that.

Copy link
Member

Choose a reason for hiding this comment

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

And probably change to a .includes('Open')

lib/mapUtils.js Outdated
const closingMin = closingHourMinSplit[1];

// Convert to 24 hour time
if (openingSuffix === 'pm') {
Copy link
Member

Choose a reason for hiding this comment

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

General comment: We'll definitely give the NPO a strict guide on how they have to input hours data into Airtable, but when possible, we should try to leave a bit of room for error. As you noticed when pulling the phone numbers from the original sheet, the data was quite messy and inconsistent and I had to clean everything up first.

In places like this, could you try to make things like this not case sensitive (like maybe add a toLowerCase toopeningSuffix in case they try to enter things with uppercase PM)? Maybe same for the abbreviations in daysOfTheWeek.

Also, the original spacing format they gave us was very inconsistent (some with spaces before hyphens, some with spaces after, some both/neither etc). To correct for this, could you add something like hours.replace('- ', '-').replace(' -', '-') to get rid of spaces around hyphens in both hours and days before you even try to parse it to make sure it doesn't break? (also there might be a cleaner way to do this than replace)
image

I know these seem like small details, but if it takes a few small changes to avoid NPO user error causing crashes, let's try to make their experiences easier whenever we can : )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the airtable it looked like the hours were inputted in the form of:
7:30am-10pm Daily
7am-10pm Mon-Sat, 9am-9pm Sun

Would it be safe for me to assume that it would follow the timeInterval Day format (regardless of weird spacing issues)? I haven't accounted for the ones you linked in the picture

</TouchableOpacity>
{/* Phone Number */}
<InLineContainer style={{ alignItems: 'center', paddingBottom: 32 }}>
<FontAwesome5 name="phone" solid size={24} color={Colors.black} />
Copy link
Member

Choose a reason for hiding this comment

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

Change all instances of Colors.black to activeText

return (
<View style={{ display: 'flex', flexDirection: 'row' }}>
{/* Days of the week */}
<View style={{ width: '50%' }}>
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Maybe adjust this 50-50 ratio to more 40-60 just to make sure we don't get weird wrapping/truncating edge cases on narrower screens.

</View>
<View style={{ width: '50%' }}>
{daysOfTheWeekFull.map((day, _) => {
if (day == todaysDay) {
Copy link
Member

Choose a reason for hiding this comment

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

I think we should make sure all of these are === instead of == bc eslint haha

lib/mapUtils.js Outdated
@@ -43,3 +43,156 @@ export async function getProductData(store) {
export const getMaxWidth = function sync(screenWidth, ebt, seeProducts) {
return screenWidth - 32 - 12 + (ebt ? -48 : 0) + (seeProducts ? -130 : 0);
};

export function constructHoursDict(hours) {
const daysOfTheWeek = {
Copy link
Member

Choose a reason for hiding this comment

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

Wondering if we should consolidate some of these things into a file in constants? Lots of repeated code between these different functions.

Copy link
Member

@wangannie wangannie left a comment

Choose a reason for hiding this comment

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

SO SORRY here are some more fun edge cases :')


const parseHours = () => {
// Case: hours = "Open 24/7"
if (hours === 'Open 24/7' || hours === 'Store hours unavailable') {
Copy link
Member

Choose a reason for hiding this comment

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

Hm then I'm not sure why it isn't working? When I make a field in Airtable blank and tap on that store on the map, it breaks.
image

I think it's bc parseHours isn't called when trying to find if the store is currently open. You'd also need to check when computeStoreOpen is called if hours is blank or somehow invalid. I would suggest maybe puttting a try/catch around the whole computeStoreOpen for any parsing/date issues, and returning 'Store hours unavailable' in the catch.

lib/mapUtils.js Outdated
let tmrwDay = daysOfTheWeekFull[tmrwDayIndex];
let tmrwStoreHours = hoursPerDayDict[tmrwDay];
// Loop until next open day is found.
while (tmrwStoreHours === 'Closed') {
Copy link
Member

Choose a reason for hiding this comment

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

NICE that you catch this! I was expecting this edge case 9am-7pm Sun, 9am-8pm Tu-Thurs, Closed Fri, Closed Sat to break but that's very cool that it works! Still, I'd probably rename this to be more specific like next opening day bc 'tmrw' is a bit deceiving.

@@ -55,89 +64,108 @@ export default function StoreCard({
});
};

const storeOpenStatus = computeStoreOpen(store.storeHours);
Copy link
Member

Choose a reason for hiding this comment

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

I'm getting REALLY SLOW loading! Is there a way we can make this only call once? It seems to be called with every movement on the map and gets REALLY REALLY slow on the store list screen. When I type to search, it's so slow that it doesn't even respond at all. It gets to a point where I can't load all the stores on storelist and then it fully stops responding altogether like I can't tap map icons or close the Find a Store page.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah good point! I removed one call in the StoreDetailsScreen and pass the summary line in as a prop from StoreCard. Hopefully this helps!

{`${distance} miles away`}
</Caption>
)}
<TouchableHighlight
Copy link
Member

Choose a reason for hiding this comment

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

Ok sorry this totally my bad this was a bad suggestion. This turns black on clicking 😅 TouchableOpacity would work just fine instead since it's on StoreCard and not transparent to the whole mapscreen now 😂

lib/mapUtils.js Outdated
@@ -155,7 +152,8 @@ function computeNextOpenDay(todaysDayIndex, hoursPerDayDict) {
return 'Closed until '
.concat(tmrwDay)
.concat(' ')
.concat(tmrwOpeningTime);
.concat(tmrwOpeningTime)
.replace('12am', 'Midnight');
Copy link
Member

Choose a reason for hiding this comment

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

I'm p sure you actually wouldn't need this here or in 'Open now until' below bc doing this in the final StoreHours does the final change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I added it there because I don't display StoreHours on the StoreCard (this method primarily gives you the summary, aka "Open 24/7" or whatever, and StoreHours gives you the whole schedule)

Copy link
Member

@wangannie wangannie left a comment

Choose a reason for hiding this comment

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

Small details I forgot to submit

if (hoursStrSplit.length === 2 && hoursStrSplit.includes('Daily')) {
const time = hoursStrSplit[0];

daysOfTheWeekFull.map((day, _) => {
Copy link
Member

Choose a reason for hiding this comment

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

WRT the 'Each child in an array should have a unique "key" prop.' issue, could you set key={day} since that should (hopefully) always be unique from the dictionary? Or you could do this like renderProductList in ProductsScreen but eslint will yell at you for Do not use Array index in keys

    return products.map((product, i) => {
      return (
        <ProductCard
          key={i}
          product={product}
          navigation={this.props.navigation}
          store={store}
          displayPoints
        />
      );
    });

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added keys to all the component children of the loop in StoreHours and it seems to get rid of the warning so we good!

Copy link
Member

@wangannie wangannie left a comment

Choose a reason for hiding this comment

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

GREAT WORK THU thank you for all of your time and energy on this!

Copy link
Contributor

@tommypoa tommypoa left a comment

Choose a reason for hiding this comment

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

Oh damn this is such a great PR Thu! Really helpful documentation and very clean/nice code to read! Left literally 2 nits/question that I don't think it's crucial to address in this PR but probably nice – otherwise good to go! Awesome!

Comment on lines +34 to +43
<TouchableOpacity style={{ paddingBottom: 32 }}>
<InLineContainer style={{ alignItems: 'center' }}>
<FontAwesome5
name="directions"
size={24}
color={Colors.activeText}
/>
<Body style={{ marginLeft: 12 }}>{store.address}</Body>
</InLineContainer>
</TouchableOpacity>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a functionality that is associated with this TouchableOpacity? Best guess is that it should mimic that of what happens when you do it for the BottomSheet, but it's doing nothing for now.

return hoursList(hoursPerDayDict);
}
console.log(
'[Storehours] parseHours: Issue parsing store hours. Hours were: ',
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: StoreHours

@wangannie wangannie merged commit 27c7777 into master Apr 18, 2020
@wangannie wangannie deleted the thu/hours branch April 18, 2020 19:47
Copy link
Collaborator

@annieyro annieyro left a comment

Choose a reason for hiding this comment

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

amazing @thumn !! I'm not sure on the React details but if things are still kind of slow, I would guess that you don't want StoreHours to be a functional component, and instead have it maintain its state so it doesn't re-parse/construct the dictionary every time.

Another alternative is to parse the hours in lib/mapUtils's updateStoreData function, and set a new property that is an enum telling you what category of 'hours' to show the parsed hours is (seems like it's something like 24/7, daily, or other?). Then you can pass the flag / enum property, check that, and then manipulate the store.hours property as needed (at that point it could be a string or a dictionary).

Also, a note for the future @wangannie : for deciding whether to extract constants/etc to files, it’s usually fine to keep it in the lib/_utils file unless the constants are used independent of the lib/_utils; otherwise it’s just always gonna be two imports: one from the constants/file and one from the lib/_utils file.

Another reason is that constants are hardcoded values that might be updated (e.g rewardPointValue)

From what I can tell it wasn't strictly necessary to bring the days of the week out but it's fine either way LOL

@wangannie
Copy link
Member

Getting this in the StoreList sometimes

VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {
  "contentLength": 4548,
  "dt": 754,
  "prevDt": 662,
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants