profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/yenly/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Yenly yenly San Francisco Bay Area https://www.yenly.wtf Software Engineer

yenly/foamy-nextjs 45

Basic Foam + NextJS with MDX starter for building a digital garden

yenly/api_sandbox 0

This is a simple flask app to play with APIs and to practice parsing data.

yenly/codelab_friendlychat 0

Finished Firebase Web Codelab: FriendlyChat. Test it out and leave me a friendly message.

yenly/coding-fonts 0

https://coding-fonts.css-tricks.com/

yenly/css-in-readme-like-wat 0

Style your readme using CSS with this simple trick

yenly/digital-gardeners 0

Resources, links, projects, and ideas for gardeners tending their digital notes on the public interwebs

startedkentcdodds/mdx-bundler

started time in 5 days

PullRequestEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Sort list items

 function ShoppingList({ listId }) {     setFilter(e.target.value);   }; +  const getDaysToPurchase = (item) => {+    let nextPurchaseDate;+    if (item.lastPurchaseDate?.seconds) {+      // if the item has been purchased before, next purchase date is [purchaseInterval] days from the lastPurchaseDate+      nextPurchaseDate = DateTime.fromSeconds(+        item.lastPurchaseDate.seconds,+      ).plus({ days: item.purchaseInterval });+    } else if (item.createdAt?.seconds) {+      // if there's no purchase history, estimate it will be bought [purchaseInterval] days from when item was created+      // (user provides this info at item creation)+      nextPurchaseDate = DateTime.fromSeconds(item.createdAt.seconds).plus({+        days: item.purchaseInterval,+      });+    } else return null;++    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));+    const daysRemaining = nextPurchaseDate.diff(currentDate, ['days']);+    return Math.round(daysRemaining.as('days'));+  };++  const isItemInactive = (item) => {+    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));++    if (item.lastPurchaseDate?.seconds) {+      // if an item has been purchased before, compare the time elapsed since lastPurchaseDate to the purchaseInterval+      return (+        currentDate+          .diff(DateTime.fromSeconds(item.lastPurchaseDate.seconds), ['days'])+          .as('days') >=+        2 * item.purchaseInterval+      );+    } else if (item.createdAt?.seconds) {+      // if an item has never been purchased, do the same calculating using createdAt date instead of lastPurchaseDate+      return (+        currentDate+          .diff(DateTime.fromSeconds(item.createdAt.seconds), ['days'])+          .as('days') >=+        2 * item.purchaseInterval+      );+    } else return null;+  };

You can also use the logical nullish assignment, ??= but I haven't seen it in codebases yet.

anderswift

comment created time in a month

PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Sort list items

 BEM conventions we are using:   margin: 0;   list-style: none; }++.visually-hidden {+  clip: rect(0 0 0 0);+  clip-path: inset(50%);+  height: 1px;+  overflow: hidden;+  position: absolute;+  white-space: nowrap;+  width: 1px;

TIL 👀

anderswift

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Sort list items

 function ShoppingList({ listId }) {     setFilter(e.target.value);   }; +  const getDaysToPurchase = (item) => {+    let nextPurchaseDate;+    if (item.lastPurchaseDate?.seconds) {+      // if the item has been purchased before, next purchase date is [purchaseInterval] days from the lastPurchaseDate+      nextPurchaseDate = DateTime.fromSeconds(+        item.lastPurchaseDate.seconds,+      ).plus({ days: item.purchaseInterval });+    } else if (item.createdAt?.seconds) {+      // if there's no purchase history, estimate it will be bought [purchaseInterval] days from when item was created+      // (user provides this info at item creation)+      nextPurchaseDate = DateTime.fromSeconds(item.createdAt.seconds).plus({+        days: item.purchaseInterval,+      });+    } else return null;++    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));+    const daysRemaining = nextPurchaseDate.diff(currentDate, ['days']);+    return Math.round(daysRemaining.as('days'));+  };++  const isItemInactive = (item) => {+    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));++    if (item.lastPurchaseDate?.seconds) {+      // if an item has been purchased before, compare the time elapsed since lastPurchaseDate to the purchaseInterval+      return (+        currentDate+          .diff(DateTime.fromSeconds(item.lastPurchaseDate.seconds), ['days'])+          .as('days') >=+        2 * item.purchaseInterval+      );+    } else if (item.createdAt?.seconds) {+      // if an item has never been purchased, do the same calculating using createdAt date instead of lastPurchaseDate+      return (+        currentDate+          .diff(DateTime.fromSeconds(item.createdAt.seconds), ['days'])+          .as('days') >=+        2 * item.purchaseInterval+      );+    } else return null;+  };

Not a must for this PR. This is a common pattern to create a new variable based on two potentials (one being null) so then it's not necessary to repeat lines 90-95 and 98 -103. It works because item.createdAt?.seconds is never null as a fallback variable.

const estimateDate = item.lastPurchaseDate?.seconds || item.createdAt?.seconds;
  const isItemInactive = (item) => {
    const estimateDate = item.lastPurchaseDate?.seconds || item.createdAt?.seconds;

    if (estimateDate) {
      return (
        currentDate
          .diff(DateTime.fromSeconds(estimateDate), ['days'])
          .as('days') >=
        2 * item.purchaseInterval
      );
    }
  };
anderswift

comment created time in a month

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Sort list items

 function ShoppingList({ listId }) {     setFilter(e.target.value);   }; +  const getDaysToPurchase = (item) => {+    let nextPurchaseDate;+    if (item.lastPurchaseDate?.seconds) {+      // if the item has been purchased before, next purchase date is [purchaseInterval] days from the lastPurchaseDate+      nextPurchaseDate = DateTime.fromSeconds(+        item.lastPurchaseDate.seconds,+      ).plus({ days: item.purchaseInterval });+    } else if (item.createdAt?.seconds) {+      // if there's no purchase history, estimate it will be bought [purchaseInterval] days from when item was created+      // (user provides this info at item creation)+      nextPurchaseDate = DateTime.fromSeconds(item.createdAt.seconds).plus({+        days: item.purchaseInterval,+      });+    } else return null;++    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));+    const daysRemaining = nextPurchaseDate.diff(currentDate, ['days']);+    return Math.round(daysRemaining.as('days'));+  };++  const isItemInactive = (item) => {+    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));

I noticed DateTime.fromSeconds(Math.floor(Date.now() / 1000)); is repeated a few times. Not a must for this PR. It may be helpful to declare this once outside of these functions and then you can use currentDate directly so then line 33 will change to:

const newPurchaseDate = currentDate;
``
anderswift

comment created time in a month

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Sort list items

 function ShoppingList({ listId }) {     setFilter(e.target.value);   }; +  const getDaysToPurchase = (item) => {+    let nextPurchaseDate;+    if (item.lastPurchaseDate?.seconds) {+      // if the item has been purchased before, next purchase date is [purchaseInterval] days from the lastPurchaseDate+      nextPurchaseDate = DateTime.fromSeconds(+        item.lastPurchaseDate.seconds,+      ).plus({ days: item.purchaseInterval });+    } else if (item.createdAt?.seconds) {+      // if there's no purchase history, estimate it will be bought [purchaseInterval] days from when item was created+      // (user provides this info at item creation)+      nextPurchaseDate = DateTime.fromSeconds(item.createdAt.seconds).plus({+        days: item.purchaseInterval,+      });+    } else return null;++    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));+    const daysRemaining = nextPurchaseDate.diff(currentDate, ['days']);+    return Math.round(daysRemaining.as('days'));+  };++  const isItemInactive = (item) => {+    const currentDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));++    if (item.lastPurchaseDate?.seconds) {+      // if an item has been purchased before, compare the time elapsed since lastPurchaseDate to the purchaseInterval+      return (+        currentDate+          .diff(DateTime.fromSeconds(item.lastPurchaseDate.seconds), ['days'])+          .as('days') >=+        2 * item.purchaseInterval+      );+    } else if (item.createdAt?.seconds) {+      // if an item has never been purchased, do the same calculating using createdAt date instead of lastPurchaseDate+      return (+        currentDate+          .diff(DateTime.fromSeconds(item.createdAt.seconds), ['days'])+          .as('days') >=+        2 * item.purchaseInterval+      );+    } else return null;+  };++  const sortListItems = (itemA, itemB) => {+    // if items being compared are both inactive, or neither is inactive, sort by days until next purchase+    if (+      (itemA.status === 'inactive' && itemB.status === 'inactive') ||+      (itemA.status !== 'inactive' && itemB.status !== 'inactive')+    ) {+      if (itemA.daysToPurchase < itemB.daysToPurchase) return -1;+      if (itemA.daysToPurchase > itemB.daysToPurchase) return 1;++      // if we've made it this far, days to purchase is the same for both items so alphabetize+      return itemA.itemName.localeCompare(itemB.itemName, 'en', {+        sensitivity: 'base',+        ignorePunctuation: true,+      });+    } else {+      // if one item is inactive and the other is not, bump down the inactive item+      if (itemA.status === 'inactive') return 1;+      if (itemB.status === 'inactive') return -1;+    }+  };

👏🏽 Nicely done. I always find it difficult to wrap my head around sorting. Recently my team lead had said to me, "No one should sort alone." 😅 😄 When your sorts become more complicated and all the nested if elses become too hairy to reason about. This is for future reference to use switch statement for pattern matching.

anderswift

comment created time in a month

PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Sort list items

 function ShoppingList({ listId }) {           </div>            <ul className="shopping-list__list list-reset">-            {listItems.docs-              .filter((doc) =>-                new RegExp(filter, 'i').test(doc.data().itemName),-              )-              .map((doc) => (-                <ShoppingListItem-                  key={doc.id}-                  listId={listId}-                  itemId={doc.id}-                  item={doc.data()}-                  checkAsPurchased={checkAsPurchased}-                />-              ))}+            {itemsToDisplay.map((item) => (+              <ShoppingListItem+                key={item.id}+                item={item}+                checkAsPurchased={checkAsPurchased}+              />+            ))}

👏🏽

anderswift

comment created time in a month

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Delete shopping item

 function App() {    return (     <Router>-      <div className="App container">+      <div+        className="App container"+        aria-hidden={showModal} // if showModal is true, hide the rest of the app

This is an interesting approach. I'm not sure what the right way to approach this as well. Will have to research it.

connietran-dev

comment created time in a month

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Delete shopping item

 function App() {     });   } +  const handleModalOpen = (item, itemId) => {+    // set item to be deleted to item object and set itemId since it's separate from Firestore+    setItemToDelete({ ...item, id: itemId });+    setShowModal(true);

This is an interesting approach to save the item object for deletion in App state before doing so.

connietran-dev

comment created time in a month

PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Delete shopping item

+import { useEffect, useRef } from 'react';+import './Modal.css';++const Modal = ({ showModal, handleModalClose, deleteItem, item }) => {+  const toggleModalClassName = showModal ? 'display-block' : 'display-none';++  const cancelRef = useRef();+  const deleteRef = useRef();++  useEffect(() => {+    if (showModal) {+      // when modal opens, add eventListeners and put initial focus on "No, Cancel"+      document.addEventListener('keydown', handleKeyEvents);+      cancelRef.current.focus();+    } else {+      document.removeEventListener('keydown', handleKeyEvents);+    }+  }, [showModal]);++  const handleKeyEvents = (e) => {+    // close modal if user hits Escape (27)+    if (e.keyCode === 27) {+      handleModalClose();+    }++    // this keeps keyboard focus within modal. if user hits Tab (9)+    if (e.keyCode === 9) {+      // ...and NOT shift while on Delete, put focus on Cancel+      if (!e.shiftKey && document.activeElement === deleteRef.current) {+        cancelRef.current.focus();+        return e.preventDefault();+      }++      // ...and shift while on Cancel, put focus on Delete+      if (e.shiftKey && document.activeElement === cancelRef.current) {+        deleteRef.current.focus();+        return e.preventDefault();+      }+    }+  };

By moving the handleKeyEvents function into the useEffect and adding a clean up function, we ensure only we are only adding the eventlistener when it's needed and removing it when the modal component unmounts. Here's is the recommendation on how to handle 'exhaustive-deps' lint rule.

  useEffect(() => {
    const handleKeyEvents = (e) => {
      // close modal if user hits Escape (27)
      if (e.keyCode === 27) {
        handleModalClose();
      }

      // this keeps keyboard focus within modal. if user hits Tab (9)
      if (e.keyCode === 9) {
        // ...and NOT shift while on Delete, put focus on Cancel
        if (!e.shiftKey && document.activeElement === deleteRef.current) {
          cancelRef.current.focus();
          return e.preventDefault();
        }

        // ...and shift while on Cancel, put focus on Delete
        if (e.shiftKey && document.activeElement === cancelRef.current) {
          deleteRef.current.focus();
          return e.preventDefault();
        }
      }
    };
    if (showModal) {
      // when modal opens, add eventListeners and put initial focus on "No, Cancel"
      document.addEventListener('keydown', handleKeyEvents);
      cancelRef.current.focus();
    }
    return () => document.removeEventListener('keydown', handleKeyEvents);
  }, [handleModalClose, showModal]);
connietran-dev

comment created time in a month

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Delete shopping item

 BEM conventions we are using:   margin: 0;   list-style: none; }++.display-block {+  display: block;+}++.display-none {+  display: none;+}

+1 on meaningful naming.

connietran-dev

comment created time in a month

PullRequestReviewEvent

startedvercel/og-image

started time in a month

startedvasturiano/react-globe.gl

started time in a month

PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Update purchase interval when checking off item

 function ShoppingList({ listId }) {     db.collection(`lists/${listId}/items`).orderBy('purchaseInterval', 'asc'),   ); -  const handleCheck = (e) => {-    const itemId = e.target.value;+  // Helper function to get the latest interval between purchases (expects Luxon date objects)+  const getLatestInterval = ({ lastPurchaseDate, newPurchaseDate }) => {+    const duration = newPurchaseDate.diff(lastPurchaseDate, ['days']);+    return Math.round(duration.as('days'));+  };++  const checkAsPurchased = (itemId, item) => {+    // convert lastPurchaseDate from firestore and JS current time to Luxon DateTime objects+    const lastPurchaseDate = item.lastPurchaseDate?.seconds+      ? DateTime.fromSeconds(item.lastPurchaseDate.seconds)+      : null; // for new items, lastPurchaseDate will be null so keep it null+    const newPurchaseDate = DateTime.fromSeconds(Math.floor(Date.now() / 1000));++    // if lastPurchaseDate is null (item not yet purchased), a latest interval can't be+    // calculated, so in that case set latestInterval to the current purchaseInterval+    const latestInterval =+      lastPurchaseDate === null+        ? item.purchaseInterval+        : getLatestInterval({ lastPurchaseDate, newPurchaseDate });++    const newPurchaseInterval = calculateEstimate(+      item.purchaseInterval,+      latestInterval,+      item.numberOfPurchases,+    );+

👏🏽

anderswift

comment created time in a month

PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Add filter input to the shopping list

 function ShoppingList({ listId }) {       );     } else {       return (-        <ul className="shopping-list__list list-reset">-          {listItems.docs.map((doc) => (-            <ShoppingListItem-              key={doc.id}-              listId={listId}-              itemId={doc.id}-              item={doc.data()}-              handleCheck={handleCheck}+        <div>+          <div className="filter">+            <label htmlFor="filterInput" className="filter__label label">+              Filter items+            </label>+            <input+              type="text"+              id="filterInput"+              name="filterInput"+              value={filter}+              onChange={handleInput}+              className="filter__text-field text-field"             />-          ))}-        </ul>+            <button+              type="button"+              aria-label="clear"+              className="filter__button"+              onClick={() => setFilter('')}+            >+              Clear Filter+            </button>+          </div>+          <ul className="shopping-list__list list-reset">+            {listItems.docs+              .filter((doc) =>+                new RegExp(filter, 'i').test(doc.data().itemName),+              )

I typically see the use of array .include() method but this is also very nice!

nick-zanetti

comment created time in a month

PullRequestReviewEvent
PullRequestReviewEvent

Pull request review commentthe-collab-lab/tcl-26-smart-shopping-list

Add filter input to the shopping list

 function ShoppingList({ listId }) {       });   }; +  const handleInput = (e) => {

Naming is the toughest part of programming. This resource was shared by another mentor in previous cohorts.

nick-zanetti

comment created time in a month

startedpbeshai/use-query-params

started time in a month

PullRequestReviewEvent

push eventyenly/css-in-readme-like-wat

Yenly Ma

commit sha ae945b9e2200052a266d4417bf98bf9d7640e7e4

Revert "Link to the SVG and add explanation (#15)" This reverts commit b4ef8cd13176a9ca70407ee48a506ac8444e999a.

view details

push time in a month