Code Monkey home page Code Monkey logo

tutorbook's People

Contributors

avoajaugochukwu avatar dependabot[bot] avatar evanyang1 avatar hodovani avatar jkorum avatar kartikcho avatar nicholaschiang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

tutorbook's Issues

fix: update input styling for Poppins font-family

Using Poppins as our default font-family seems to be causing some issues on the taller letters in input elements (e.g. the MDC Select, TextField, and our own custom SubjectSelect). As pictured below, the g's are all cut off:

image

We set Poppins as the default in src/styles/global.scss when we @use the @material/typography module:

@use '@material/typography' with (
  $font-family: unquote('Poppins, sans-serif'),
  $styles-button: (
    font-size: 1.05rem,
    font-weight: map.get($font-weight-values, bold),
    letter-spacing: 0.0125rem,
    text-transform: none,
  ),
  $styles-headline2: (
    font-size: 3.5rem,
    font-weight: map.get($font-weight-values, bold),
    letter-spacing: -0.125rem,
  ),
);

Debug and remove the "checkbox is uncontrolled" error

We receive this error message (due to our SubjectSelect) whenever a subject is selected from our drop-down select menu:

index.js:1 Warning: A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components
    in input (created by Function)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by ForwardRef(CheckboxRoot))
    in ForwardRef(CheckboxRoot) (created by withRipple(Object))
    in Ripple (created by class_1)
    in class_1 (created by withRipple(Object))
    in withRipple(Object) (created by Function)
    in Function (at subject-select.tsx:217)
    in i (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by ForwardRef(IconRoot))
    in ForwardRef(IconRoot) (created by Icon)
    in Icon (created by Function)
    in Function (at subject-select.tsx:216)
    in li (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (created by withRipple(Function))
    in Ripple (created by class_1)
    in class_1 (created by withRipple(Function))
    in withRipple(Function) (at subject-select.tsx:211)
    in ul (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at subject-select.tsx:161)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in PortalChild (created by Function)
    in Function (at subject-select.tsx:155)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at subject-select.tsx:154)
    in SubjectSelect (at covid-form.tsx:108)
    in form (at covid-form.tsx:197)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at covid-form.tsx:184)
    in Form (at covid-pupil-form.tsx:43)
    in div (at covid-pupil-form.tsx:42)
    in div (at covid-pupil-form.tsx:41)
    in PupilForm (at pupils.tsx:12)
    in PupilsPage (at _app.tsx:10)
    in DBProvider (at _app.tsx:9)
    in App
    in Container (created by AppContainer)
    in AppContainer

This needs to be addressed must likely by modifying the SubjectSelect component definition in src/subject-select/lib/subject-select.tsx such that the Checkbox is always a "controlled component."

feat: add safeguarding

Problems

The biggest concern in all nonprofits like this one is safeguarding. How are you going to run background checks on your volunteer tutors? How does one know if their tutor is qualified? Who is liable if something terrible happens between the volunteer tutor and the student that you connected?

Solutions

Our recommended safeguarding flow looks like this:

Students must enter their parent’s contact info before sending lesson requests. Parents then receive emails for each lesson request with:

  • The tutor’s profile (that the tutor inputs on our site)
  • The tutor’s verified (via an academic email address) university position (e.g. “student at Duke University”)
  • Links to the tutor’s (verified) social accounts (e.g. their LinkedIn, Facebook, Instagram)

An option to run (and pay for) various background checks (e.g. a state-wide sex-offender lookup or DBS check)

Note that once a background check has been paid for by one parent, that background check is added to the site and becomes available to everyone at no extra cost.

Once the parent signs off on a tutor, the student will meet with them via Bramble (no direct contact info is ever shared).

Parents are also encouraged to join the first tutoring session.

Remove pre-filled workaround for notch styling

Right now (in @tutorbook/subject-select), I'm using a this.state.inputValueWorkaround field to ensure that the input value contains a non-breaking space (i.e. &nsbp; or \xa0) whenever there are subjects selected (i.e. when there are chips within the TextField).

Workaround below (in src/subject-select/lib/subject-select.tsx on line 87):

  /**
   * Workaround for styling the input as if it has content. If there are 
   * subjects selected (in the given `subjects` object), this will return a
   * string containing a space (`' '`) so that the `TextField` styles itself as
   * if it were filled.
   * @todo Ideally, remove this workaround. But instead, make the `&nsbp;` 
   * actually show up as a non-breaking space (i.e. nothing).
   * @see {@link https://github.com/jamesmfriedman/rmwc/issues/601}
   * @return {string} The input value (either `' '` or `''`).
   */
  getInputValue(
    subjects: { [subject: string]: boolean } = this.state.subjects,
  ): string {
    const selected = Object.values(subjects).reduce((a, b) => a || b, false);
    return selected ? '\xa0' : '';
  }

See this issue and this issue for more info on how I went about implementing our SubjectSelect component.

fix: address Vercel Now Firebase Admin API key error

We're using the Firebase Admin Node.js SDK on our serverless API functions (with Next.js) and have added the API key as a FIREBASE_ADMIN_KEY environment variable but it seems to be formatted incorrectly on Vercel Now.

I just created an example of our issue:

To reproduce the error, open the logs for the /api/admin serverless function on Vercel and invoke the function (i.e. by opening this link). You should see the following error from the Firebase Admin Node.js SDK:

2020-04-29T16:27:44.172Z    7d6a342a-b7ec-4b25-9eb0-136dab0c3c56    ERROR    FirebaseAppError: Failed to parse private key: Error: Invalid PEM formatted message.
    at FirebaseAppError.FirebaseError [as constructor] (/var/task/node_modules/firebase-admin/lib/utils/error.js:42:28)
    at FirebaseAppError.PrefixedFirebaseError [as constructor] (/var/task/node_modules/firebase-admin/lib/utils/error.js:88:28)
    at new FirebaseAppError (/var/task/node_modules/firebase-admin/lib/utils/error.js:123:28)
    at new ServiceAccount (/var/task/node_modules/firebase-admin/lib/auth/credential.js:142:19)
    at new ServiceAccountCredential (/var/task/node_modules/firebase-admin/lib/auth/credential.js:68:15)
    at Object.cert (/var/task/node_modules/firebase-admin/lib/firebase-namespace.js:219:58)
    at Module.Wyqg (/var/task/.next/serverless/pages/api/admin.js:262:73)
    at __webpack_require__ (/var/task/.next/serverless/pages/api/admin.js:23:31)
    at Server.module.exports.HQqF.__webpack_exports__.default (/var/task/.next/serverless/pages/api/admin.js:194:28)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  errorInfo: {
    code: 'app/invalid-credential',
    message: 'Failed to parse private key: Error: Invalid PEM formatted message.'
  },
  codePrefix: 'app'
}

This is definitely an issue with Vercel’s environment variables, as it works fine in my development environment (i.e. running next dev works fine).

See the Firebase docs for more info on how that's working.

Polish availability input's specificity

The problem
Our existing availability input constrains the user to 3-hr blocks of time and can be rather time-consuming to check (or uncheck after a "Select all" click) each box.

image

The solution
We'll need an input that will:

  • Replace the checkbox-based one pictured above.
  • Allow for more specificity (i.e. users aren't constrained to set 3-hour blocks). For example, I should be able to say that I'm free from 12:30 PM to 1:46 PM on Monday.
  • Emulate the week view on Google Calendar.
  • Have an option to sync with iCal or Google Calendar (e.g. I import my existing calendar items and any time that isn't already "busy" is added as availability).

I already pinged our design team on this, hopefully they'll have a solution designed that we can then implement soon.

fix: add i18n to grade levels

hey guys, we need to add a grading to all the courses like for example, we could put 'senior/grade 12' instead of just senior. Like this students from diff countries can have a better understanding of the system.

Debug and remove "prop id didn't match" error

We need to get rid of this error that shows up due to our ScheduleInput every time our home page is loaded:

Warning: Prop `id` did not match. Server: "toggle--gxfg3t7xwo" Client: "toggle--ki0j10p2l"
    in input (created by Function)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by ForwardRef(CheckboxRoot))
    in ForwardRef(CheckboxRoot) (created by withRipple(Object))
    in Ripple (created by class_1)
    in class_1 (created by withRipple(Object))
    in withRipple(Object) (created by Function)
    in Function (at schedule-input.tsx:154)
    in td (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at schedule-input.tsx:153)
    in tr (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at schedule-input.tsx:139)
    in tbody (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at schedule-input.tsx:136)
    in table (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at schedule-input.tsx:127)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at schedule-input.tsx:126)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in PortalChild (created by Function)
    in Function (at schedule-input.tsx:121)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at schedule-input.tsx:120)
    in ScheduleInput (at covid-form.tsx:124)
    in form (at covid-form.tsx:197)
    in div (created by ForwardRef(Tag))
    in ForwardRef(Tag) (created by Function)
    in Function (at covid-form.tsx:184)
    in Form (at hero-form.tsx:24)
    in div (at hero-form.tsx:23)
    in div (at hero-form.tsx:22)
    in HeroForm (at pages/index.tsx:13)
    in IndexPage (at _app.tsx:10)
    in DBProvider (at _app.tsx:9)
    in App
    in Container (created by AppContainer)
    in AppContainer

See this issue for more info.

Add lang attribute to html tag

We need to add the lang tag to the top-level <html> tag via a custom Next.js Document like so:

<html lang="en">

This is going to be rather difficult to do however, as we do not want to give up SSG and SSR but we want to have a different lang tag for each different locale (i.e. the locales that are used to SSG src/pages/[locale]/*).

feat: use service workers to reduce bundle size

We want to remove any dependencies on the Firebase JavaScript SDK from our client-side bundle. That SDK is pretty big (I think it's taking up the bulk of our bundle right now) and we're only using it for Authentication and Google Analytics.

Ideally, we'll want to implement something similar to what is described here to intercept requests to the api/ endpoints and add a token parameter or an Authentication HTTP header (containing our Firebase Authentication idToken which is used by our API endpoints to authenticate our requests via the Firebase Admin Node.js SDK).

Right now, our Firebase Authentication is implemented in src/firebase/lib/ primarily in src/firebase/lib/user.ts. We use React Context to make the current User object accessible to everything in the React tree (see src/pages/_app.tsx where we add a UserProvider to the top of the Next.js app).

style: choose material design react implementation

I thought this would be easy, but it turns out that Google doesn't have a maintained implementation of their Material Design Guidelines for React. They used to but it's no longer maintained.

There are many third-party implementations though:

And probably many more that aren't even maintained enough to be listed here as viable options. After reading a bunch of blog posts (listed below), I'm thinking I'm going to go with RMWC instead of the ever-more-popular Material UI:

fix(subject-select): update search clearing

Our SubjectSelect React component (defined in src/subject-select) takes the current input and uses it to search all the possible subject that then appear in the drop-down select menu (see below).

Screenshot 2020-04-13 at 7 08 25 PM

The problem with this, however, is that once the user selects a subject from that select menu, the current search input is cleared but the search results (i.e. the subjects listed in that select menu) stay the same (see below).

Screenshot 2020-04-13 at 7 04 13 PM

The most intuitive fix here (in my opinion), is to just keep their search input full even after they select a subject from the drop-down select menu. We'll just force them to clear it when they want to see the original subject list again.

Debug Sass import file abbreviations

Current workaround in src/subject-select/lib/subject-select.module.scss:

@use '@material/chips/_index.scss' as chips;

We want it to read like (this should automatically import _index.scss and create a chips namespace):

@use '@material/chips'

See this original issue for more info.

feat: create landing/about pages

From @AlephTaw's original issue:

Version 2 Google Slides - Feel free to insert revisions, so we can collab/compare/keep a tight product to UI/UX feedback loop.Thanks!!!

Pages:

Landing Page:

Parents, Students <-> Tutors

Nav Bar: About, Meet our Team!, Contact, Volunteer, FAQs, Student Resources

Student Page:

Layout: Side-by-side - Description | Form

LogIn or signup - Description of the service … Here’s how we can help you get individualized educational support during the covid -19 … 1) Sign Up 2) … 3) One of our tutors.

Student Sign-Up:
Parent, Student:
Want to learn more about us? Meet our team, visit our FAQs, or contact us.
Fills out Form (Below):
Parent Name*
Parent Phone Number*
Parent Email*
Student Name*
Student Email*
Grade Level
City
State
Zip

Student Login:
-> Student Account: Tutor Portfoliio -> Grid of (Tutors, Subjects) Pairs w/ mini Bios and next appointment (time, day, date in student time zone), a link to their Google Drive Folder (and subfolders (one for each tutor)), ability to submit a rating: 1-5 stars + text field to submit reviews, I would like to report an issue (checkbox), to tutor records [submit], button for canceling an appointment (3 Drop Maximum: If you are having trouble matching with a tutor, contact us.)

Make an Appointment:
Subject
Grade Level
Weekly Availability (To maximize your options, include all times that you can make on a weekly basis):

Student Resources:
A collection of well formatted links with descriptions - scour
from other sites, etc.

Tutor Page:

Login or Signup to Volunteer

Sign Up:
Email*
Name*
Phone Number*
Which of the following have you attained:* In college, college, Masters, PhD
Do you have experience tutoring professionally:
Yes
No

Number of Years:* 1, 2, 3, 4, 5, +

School [Text Field]

City:
State:
Zip:

Hobbies:
Hobby 1:
Hobby 2:
Hobby 3:

Message (tutors can add their comments) [text field]:

#Notes: Tutor bios will be automatically generated as: “Hi, my name is NAME. I am currently getting/have completed my degree in DEGREE. I am volunteering with covidtutoring.org to help students everywhere regain educational access! In my free time I enjoy ___, ____, and _./ and /

Once Logged In -->

Student Roster: Grid view (Student: First Name ?, subject, next appointment)

Calendar where tutors can block off their weekly commitments (on the half hour in their local time zones).

FAQs:

What is CovidTuotoring.org / Tutorbook … or whatever.

How you we are able to offer free support

How we ensure a quality experience

General Guidelines for Protecting your privacy online

How you are in control when using our product

How we are helping our communities

Make subject selection more intuitive

I've heard from two people so far on the current behavior of our SubjectSelect component (see #17 for some more info on the first person's perspective).

I've come up with two possible behaviors for the SubjectSelect that are listed below:

  1. Clear the input field and reset the select menu when a subject is selected.
  2. Keep the input field content and the select menu state when a subject is selected (i.e. do nothing).

Right now, we're running on behavior two so that users can easily select multiple subjects in the same area (e.g. type in "Chem" and select "Chemistry H", "Chemistry", "SAT Chemistry", and "AP Chemistry" without having to re-type in "Chem").

The solution for this issue should be to run some type of A/B testing or survey to see which is actually more popular.

feat(subject-select): keep textarea on same line with chips

The ultimate goal for our SubjectSelect component is to replicate Gmail's compose to field (as pictured below):

image

It seems that Gmail uses some type of JavaScript to manually resize (set the width) of the textarea in that input element every time a new recipient is added (i.e. when a Chip is added). We, however, just put the textarea on a new line (using flex-wrap: wrap;). See picture below:

image

It'd be nice to have that textarea stay on the line as the Chips for as long as possible (i.e. until it becomes less than, say, 10px).

feat(subject-select): add support for shift-selecting

Right now, users have to manually select each subject from the drop-down menu. We want them to be able to shift-click to select and un-select many of them at the same time.

The SubjectSelect in question (located in src/subject-select/lib/subject-select.tsx):

subject-select

feat(covid-form): allow for list groups under mdc-selects

Right now, the @tutorbook/covid-form only supports Select's that use the options prop to add options to the mdc-list within the mdc-menu. We want to be able to support customizing and manually building those options as detailed here (under "Manually building the list") such that we can add mdc-list-dividers in between item grouping.

Add acceptance, unit, and performance tests

This is probably much too large to be a single GitHub issue, but I'm going to create it here if not just for the sake of consistency.

Acceptance tests
These are UI/UX tests that emulate what a user would do to ensure that the final product is fully functional. We'll probably use Cypress or the Robot Framework for these.

We'll probably also run our Google Lighthouse tests along with these UI tests (see "performance tests" for more info).

These tests will be run after every commit via Travis-CI or GitHub Actions. It will also be run automatically for every new PR.

Unit tests
There are unit tests of each class, their methods, functions, etc. These should be as small as possible and will probably be implemented using Jest, React tree snapshots, Enzyme, and similar unit testing tools.

Performance tests
Essentially, setup Travis-CI or GitHub Actions tasks to automatically run our build (without deploying anything) so that we can see how our bundle sizes change.

For PRs and other commits that are deployed by Vercel for previewing, we'll want to run Google Lighthouse tests (right now, we've got it setup with this Vercel integration but we want it to show up on GitHub).

In the future, we'll also probably want to benchmark our acceptance tests (e.g. how much are we using the CPU, how much RAM are we using, etc). But for now, I think that tracking bundle size is our main priority.

feat: subjects change based on user grade

Some feedback (from my sister and from @Simk-ops):

The subject select is a tad bit confusing b/c all the subjects available aren't in the list and it isn't intuitive to search by typing.

And I think you sister is right, the subject select is a little confusing. I feel like we can add more specifications like Algebra, Pre-Algebra, Calculus, Linear, etc. What are yout thought on this?

To address this:

  • We're going to add an optional "Your grade level" field above the "What would you like to learn?" field.
  • We'll customize the subjects shown in the subject select based on the selected grade level (e.g. we'll only show 8th grade subjects to 8th graders).

Selects should fill based on typing (like native selects)

We want our Select components to auto-select based on user input (this is already pretty common for most native browser <select> tags, but isn't yet officially supported by the MDC Select component). When the Select is focused, we should listen for keydown events and select the options that match the typed letters.

Perhaps this is beyond the scope of this repository however, and should instead be created as an issue in the @rmwc/select repository.

I've already implemented this for Tutorbook's web app. That implementation from src/app/packages/utils/index.js is included below:

       /**                                                                                                                                                                                                             
        * Attaches the given select by:                                                                                                                                                                                
        * 1. Populating it's options with the options in the `mdc-list`.                                                                                                                                               
        * 2. Listening for `keydown` events that act as shortcuts to select an                                                                                                                                         
        * option (like most native `select` elements do).                                                                                                                                                              
        * @param {HTMLElement} selectEl - The `mdc-select` element to attach.                                                                                                                                          
        * @return {external:MDCSelect} The attached `MDCSelect` instance.                                                                                                                                              
        */                                                                                                                                                                                                             
       static attachSelect(selectEl) {                                                                                                                                                                                 
           // Initialize select                                                                                                                                                                                        
           if (typeof selectEl === 'string') selectEl = $(selectEl)[0];                                                                                                                                                
           const ops = [];                                                                                                                                                                                             
           $(selectEl).find('.mdc-list-item').each(function() {                                                                                                                                                        
               MDCRipple.attachTo(this);                                                                                                                                                                               
               if (ops.indexOf(this.innerText) < 0) ops.push(this.innerText);                                                                                                                                          
           });                                                                                                                                                                                                         
           const selected = $(selectEl).find('.mdc-select__selected-text').text();                                                                                                                                     
           const select = MDCSelect.attachTo(selectEl);                                                                                                                                                                
                                                                                                                                                                                                                       
           // Helper functions                                                                                                                                                                                         
           const lastClicked = []; // Last inputted shortcut keys                                                                                                                                                      
           const selectOption = (op) => select.selectedIndex = ops.indexOf(op);                                                                                                                                        
           const listen = (event) => {                                                                                                                                                                                 
               const justClicked = event.keyCode;                                                                                                                                                                      
               const possible = ops.filter(option => {                                                                                                                                                                 
                   option = option.toUpperCase();                                                                                                                                                                      
                   var i = 0;                                                                                                                                                                                          
                   for (i; i < lastClicked.length; i++)                                                                                                                                                                
                       if (option.charCodeAt(i) !== lastClicked[i])                                                                                                                                                    
                           return false;                                                                                                                                                                               
                   return option.charCodeAt(i) === justClicked;                                                                                                                                                        
               });                                                                                                                                                                                                     
               if (possible.length === 1) {                                                                                                                                                                            
                   lastClicked.length = 0;                                                                                                                                                                             
                   selectOption(possible[0]);                                                                                                                                                                          
               } else if (possible.length > 1) {                                                                                                                                                                       
                   lastClicked.push(justClicked);                                                                                                                                                                      
               }                                                                                                                                                                                                       
           };                                                                                                                                                                                                          
                                                                                                                                                                                                                       
           // Render empty selects even when val is null, undefined, or false                                                                                                                                          
           if (selected !== '') selectOption(selected);                                                                                                                                                                
                                                                                                                                                                                                                       
           // Listen for shortcut keys (when select is in focus)                                                                                                                                                       
           $(selectEl).on('focusin', () => $(document).keydown(listen));                                                                                                                                               
           $(selectEl).on('focusout', () => $(document).off('keydown', listen));                                                                                                                                       
                                                                                                                                                                                                                       
           return select;                                                                                                                                                                                              
       }                                                                                                                                                                                                               

feat: migrate to React using Typescript

We've decided to use React with Typescript for this project for a couple of reasons:

  • Scalability - Used by Walmart and other huge organizations.
  • Familiarity - Most people are familiar with React and Typescript is pretty easy to pick up if you know JavaScript.
  • Testable - React makes it easy to bootstrap tests and is well tested itself.
  • Document-able - Documentation is key to open source (or rather, source-available, because we're using the AGPL & CC licenses) collaboration. We still need to find an equivalent solution to our existing Tutorbook setup (where we're using jsdoc with @tutorbook/minami to automatically generate our documentation from our source code after every git commit).

fix: show error message when API calls fail

Right now, we're just catching and logging error messages but we aren't actually showing anything to the client (so they think that their request succeeded if they don't open up a console and look at the logs) in:

  • The PupilForm in src/pupil-form/lib/covid-pupil-form.tsx that calls /api/user
  • The TutorForm in src/tutor-form/lib/covid-tutor-form.tsx that calls /api/user
  • The HeroForm in src/hero-form/lib/hero-form.tsx that calls /api/user
  • The SearchResults in src/search/lib/results.tsx that calls /api/search
  • The UserDialog in src/user-dialog/lib/user-dialog.tsx that calls /api/appt

We need to show some type of pop-up dialog explaining what went wrong with each error code and how they can address it (such a dialog should also include a CTA button to open our Intercom messenger and chat with support).

feat: update footer

We want to update the footer on our website using the styling from the old website's footer so that it looks a little like:

image

It should include links to the following pages (most of which have yet to be built, but it should be pretty straight forward to create them):

  • Languages (changes the current locale)
  • "Help center" (via Intercom)
  • "Chat with us" (launches Intercom Messenger)
  • "Email us" (opens a mailto:[email protected] link)
  • "Open source" (link to this repository or the GitHub org)
  • Confluence home page (we want to be as transparent as possible)
  • Slack workspace (perhaps add a permanent invite link?)
  • Legalities (the "Privacy Policy", "Terms and Conditions of Use", etc)
  • Fundraising campaign link (@ryantzr1 should be working on that)
  • Socials (FB, Insta, Twitter, etc. @ChristopherWong274 is heading that up)
  • Report an issue (opens a new issue on GitHub)
  • Jira high-level roadmap?
  • About the team pages, etc.
  • Shout-out to partner's websites (kind of how the UK's CTI has a link to Project Access in their footer).
  • Link to the latest newsletter
  • Text field to subscribe to the newsletter?

In the future, it should also include links to the last couple of blog posts (once we get that setup using Ghost).

Add custom error page

Read the Next.js documentation on custom error pages and implement our own custom error page using the styling from the rest of the site (e.g. use @rmwc/typography, perhaps a nice background image from our web assets, etc).

Note that this error page should have as little text as possible (because it has to be static and thus we can't use react-intl for i18n). Or just include the error message in all of our supported locales (see the list in src/intl/config.json).

Make timeslot strings human readable (in availability select)

Right now, our Timeslot strings look something like this:

Mondays 12:00:00 PM - 1:00:00 PM
7:00:00 AM - 12:00:00 PM

Or, even worse in the TimeslotInput within our UserDialog, it looks like this:

Mondays 12:00:00 AM - 17:00:00 AM

We need to make those strings more human readable (e.g. Mondays 12 - 1 PM or 7 AM - 12 PM).

And for the ScheduleInput we'll want to make them secondary to the time of day (e.g. Mornings (7 AM - 12 PM) instead of 7:00:00 AM - 12:00:00 PM).

feat: social icons instead of plain links

Right now, the UserDialog displays plain text links for the user's social media accounts like so:

image

Ideally, we want them to be easily recognizable yet still discreet social media icons (probably all in gray and pretty small).

Consult with our design team (e.g. @kryssym) to review your work once you're finished.

Polish i18n support

Goals

We want to be as global as possible which means supporting as many locales as we can find translators for. We need to de-couple the the translation text from our code (i.e. static content should be moved away from the codebase).

Options

I've researched some options for different i18n libraries but they all seem to have their own setbacks:

  • next-i18next
    • This seems like a super easy way to build i18n support because it was made with Next.js in mind.
    • This however, requires us to use a custom server.js which we do not want to do (we won't be able to statically pre-render anything).
  • react-intl
    • Recommended solution by this blog post and this Stack Overflow answer.
    • Seems like it has everything we'll need (but it'll require a tad bit more work to setup than next-i18next).
    • Note that this is the solution that I'm currently working with and I'm going to continue using it until I come against any blockers.
  • i18next
    • Appears to take a lot of configuration work and is more general than what we need (we want a React-specific solution ideally).
  • react-i18next
    • Also appears to take more configuration work than we want to deal with right now (although it does seem to be widely used).

For now, I'm going to keep using react-intl until I come up against any blocking issues. Note that react-intl also has a Slack workspace which will be good for support concerns, etc.

fix(model): only import `Timestamp` object; not all of Firebase

See comments in src/model/lib/times.ts for more info:

     1 import * as firebase from 'firebase';                                                                                                                                                    
     2 import 'firebase/firestore';                                                                                                                                                             
     3                                                                                                                                                                                          
     4 /**                                                                                                                                                                                      
     5  * This is a painful workaround as we then import the entire Firebase library                                                                                                            
     6  * definition while we only want the `Timestamp` object.                                                                                                                                 
     7  * @todo Only import the `Timestamp` definition.                                                                                                                                         
     8  * @see {@link https://stackoverflow.com/a/57984831/10023158}                                                                                                                            
     9  */                                                                                                                                                                                      
    10 const Timestamp = firebase.firestore.Timestamp;                                                                                                                                          
    11 type Timestamp = firebase.firestore.Timestamp;                                                                                                                                           

We only want to import the Timestamp object (as defined here), not all of Firebase just to access that one class.

Also, that import on the server-side of things seems to produce a Firebase warning log (which we definitely don't want):

It looks like you're using the development build of the Firebase JS SDK.
When deploying Firebase apps to production, it is advisable to only import
the individual SDK components you intend to use.

For the module builds, these are available in the following manner
(replace <PACKAGE> with the name of a component - i.e. auth, database, etc):

CommonJS Modules:
const firebase = require('firebase/app');
require('firebase/<PACKAGE>');

ES Modules:
import firebase from 'firebase/app';
import 'firebase/<PACKAGE>';

Typescript:
import * as firebase from 'firebase/app';
import 'firebase/<PACKAGE>';

chore: rename user `parent` property to `parents`

Super easy fix (I'll probably do this one later in just a second), but we want to rename the parent property on the User and UserInterface to be parents instead (as it's an array of the user's parents's uIDs).

Make subject categories more intuitive

no need to repeat math grade1 math grade 2 etc each timeIt will be way easier if when the students select his grade, the system show hi all the subjects which are available. like no need to show him all the subjects from grade1 to 8.

Add carousel messages for each type of user

Have an image background carousel with messages geared towards each type of user (Students, teachers and parents). It will give visitors a general idea of the service upon landing.

fix: show error messages to client

The problem
Right now, if an /api/ request fails, we still show the checkmark and the client thinks that the request succeeded (unless, for some strange reason, they think to open up the console and look at the error logs).

The solution
Actually implement the /api/ endpoint requirements that are outlined here:

Each of these API functions catches all known errors and sends them back to the client in plain English (i.e. you can display res.data to the user when you receive a 400 or 500 HTTP error code).

Then, just display the error codes that are sent back by our API endpoints.

Note that you should consult with design on how to best show those error codes (as that's very much a front-end job).

fix(subject-select): move the select-menu when text-area expands

Right now, our SubjectSelect consists of a TextField (with the textarea property set to true) that contains a ChipSet with Chips for each currently selected subject.

There are many problems with this setup though (another detailed in #8) one being that the MenuSurface doesn't move when the TextField expands:

subject-select-issue

We have to somehow update the location state on that MenuSurface whenever the TextField expands such that the menu is always anchored to the start of the bottom of the TextField component.

Remove "For Developers" from Nav

Current nav bar has a link to our repo from the landing page Nav.

We should remove it till the time our repository is private since an outside developer can't see it till the project is public.

feat: verify and normalize phone number inputs

Right now, whenever we create a TextField to collect phone numbers, we make sure to set the input type to tel. But I don't think that the browser-native tel validation/verification is that great, so this issue aims to make it better by:

  1. Using a RegExp or similar to ensure that the phone number looks right.
  2. Adding a country selection menu to the right of the text field (default to +1 which is the United States' phone extension).
  3. Normalizing the phone input as the user types (much like a text field proxy to a time select). Might want to use this phone package to help out.

fix: catch user creation errors for parents too

Describe the bug
A clear and concise description of what the bug is.

We catch the auth/email-already-exists and auth/phone-number-already-exists Firebase Authentication errors when creating the pupil and tutor accounts but not when creating parent accounts.

To Reproduce
Steps to reproduce the behavior:

  1. Start a development server (see this repository's README for more info).
  2. Go to the pupil signup page (probably at http://localhost:3000/en/pupils).
  3. Sign-up once with an email address for the parent that hasn't been used before. You shouldn't see any errors yet.
  4. Sign-up again using that same email address and you'll see an error.

Proposed fix
A clear and concise description of what you expected to happen.

We should bring the error catching that is already implemented for tutor and pupil account creation into a higher-order function that we'll call to create parent accounts as well (i.e. we'll catch all of the account creation errors in that function).

See src/pages/api/user.ts for more info.

Tutors can pick their cancellation time period

When volunteers fill out the tutoring application, there can be a slot to pick a cancellation time period. For example

If Tutor A's Cancellation time period is : 3hrs then the student must cancel at least 3 hrs before the session if they wish to.

Polish timeslot input behavior and design

The problem
Our existing timeslot input is rather painful (i.e. as a user you have to read the helper text carefully to see what timeslots are available for selection) and is text-based (which makes i18n hard and forces the user to type out "Mondays from 3:00 PM to 3:30 PM").

image

The solution
Just posted to the #team-design Slack channel with this, so hopefully we'll have a solution designed soon:

Hey @channel could you help design a timeslot input that will:

  1. Replace the text-based one in the UserDialog (see above picture).
  2. Select a timeslot (e.g. "Mondays from 3:00 PM to 3:30 PM") from a given availability.

Automatically add locale in Next.js router

We want to be able to call:

import Router from 'next/router';
Router.push('/search');

And then automatically be sent to the locale-specific page (i.e. /search goes to /en/search without a redirect to /api/redirect).

I tried implementing this in src/intl/lib/router.ts but gave up after a while (you should find some useful links there to get you started on this):

import NextRouter from 'next/router';

import { IntlCache, IntlShape, createIntl, createIntlCache } from 'react-intl';
import { UrlObject } from 'url';

type Url = UrlObject | string;

/**
 * Adds the locale to the given url string or object.
 * @see {@link https://github.com/isaachinman/next-i18next/blob/master/src/utils/lng-path-corrector.ts}
 * @see {@link https://github.com/isaachinman/next-i18next/blob/master/src/router/wrap-router.ts}
 */
function addLocale(url?: Url): Url | undefined {
  const cache: IntlCache = createIntlCache();
  const intl: IntlShape = createIntl(
    {
      locale: 'TODO: Somehow grab locale from outside the React lifecycle.',
    },
    cache
  );
  if (typeof url === 'string') {
    url = `/${intl.locale}${url}`.replace(/\/$/, '');
  } else if (typeof url === 'object') {
    url.pathname = `/${intl.locale}${url.pathname}`.replace(/\/$/, '');
  }
  return url;
}

export class Router extends NextRouter {
  push(url: Url, as?: Url, options?: {}): Promise<boolean> {
    return super.push(addLocale(url), addLocale(as), options);
  }

  replace(url: Url, as?: Url, options?: {}): Promise<boolean> {
    return super.replace(addLocale(url), addLocale(as), options);
  }
}

Save formatting from textareas

Is your feature request related to a problem? Please describe.

In the "Education and Experience" field, the new lines that you enter should be preserved. For example, a user might enter the following:

- Bachelors (Hons)
- Maths Masters (Hons)
- Applied Mathematics Professional
- Risk Analyst (8 months experience) Professional Experience
- Senior Subject Matter Expert
- Mathematics (present)

But then their profile shows up in the UserDialog without any of the newlines:

image

Describe the solution you'd like

We should:

  • Preserve the newlines and other formatting inputted into textareas.
  • Scrub the input into open ended text fields (i.e. ensure there isn't any profanity).
  • Perhaps even support some markdown formatting (using an external library OFC).

feat: create and manage Algolia search indexes

Problem

While we use Google's NoSQL Firestore database for data storage, it's built-in querying capabilities are far from what we need to be able to do for our search MVP (e.g. it's incredibly difficult to search by text, synonyms, or custom Date-like objects).

Proposed Solution

We should sync our database with Algolia using GCP Cloud Functions that trigger after every document change. Then, we'll use Algolia's client-side SDK to query and filter that data, gradually exposing more filter parameters to the end user.

For our MVP, we'll need to be able to filter by:

  • The user's subjects (e.g. Show me all the users whose subjects.explicit field contains Chemistry H).
  • The user's availability (e.g. Show me all the users whose availability field contains a Timeslot whose open time is equal to or before the desired open time and whose close time is equal to or after the desired close time).

Eventually, it might be nice to filter by:

  • The user's education (e.g. Show me all Stanford tutors).
  • The user's location (e.g. I only want people who live in the United States).
  • The user's languages (e.g. Only those who speak English fluently).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.