Skip to content

Commit

Permalink
adds documentation for hooks, pages and api (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
arvind0598 committed Sep 26, 2021
1 parent 0ddd04b commit 39601d0
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 37 deletions.
27 changes: 27 additions & 0 deletions hooks/use-repo-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ type RepoData = {
isError: string;
};

/**
* @summary This function serves as an SWR fetcher for any URL
*
* @description
* useSWR requires a fetcher. The fetcher is passed all the variables that
* are part of the first parameter of useSWR. So, we just mirror that setup
* in this function, and then perform the actual fetch to the API.
*
* It's async because....there's a fetch going on.
*
* @param {string[]} args - a variable number of string parameters
* @returns a promise of a JSON response from the API
*/
const fetcher = async (...args: string[]) => {
const url = args[0];
const repo = args[1];
Expand All @@ -15,6 +28,20 @@ const fetcher = async (...args: string[]) => {
return res.json();
};

/**
* @summary This function creates a hook to get the repository search data.
*
* @description
* This hook internally uses useSWR to get the repository search data. We make
* sure that the repo is not NULL so that we don't waste time in sending data
* to the server even before anything is entered.
*
* This function is almost word-to-word off the standard documentation for
* useSWR.
*
* @param {string} repo the text to search for.
* @returns {RepoData} a wrapper around the API response with utility fields.
*/
const useRepoData = (repo: string): RepoData => {
const { data, error } = useSWR(['/api/search/', repo], repo ? fetcher : null);
return {
Expand Down
36 changes: 36 additions & 0 deletions hooks/use-responsive-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { useEffect, useState } from 'react';
import { ScreenSize, ResponsiveContext } from '../types/responsive';

/**
* @summary This function returns a string requivalent for a given screen width in pixels.
*
* @description
* This function returns a ScreenSize, which is a 1-2 letter acronym for the pixel equivalent
* of a sreen width.
*
* @param {number} width the width of the screen
* @returns the text equivalent of the width.
*/
const getSizeForWidth = (width: number): ScreenSize => {
if (width <= 600) return 'xs';
if (width <= 900) return 's';
Expand All @@ -9,16 +19,42 @@ const getSizeForWidth = (width: number): ScreenSize => {
return 'xl';
};

/**
* @summary This function creates a hook to get details about the current screen width.
*
* @description
* This hook internally uses the getSizeForWidth to get the text-equivalent of the screen
* width. It also adds an event listener on the first load, so that it makes sure we always
* have the latest screen width at all times.
*
* @returns a hook to get the latest screen size.
*/
const useResponsiveContext = () => {
const [responsive, setResponsive] = useState<ResponsiveContext>({ size: 'l', isMobile: false, isTablet: false });

/**
* @summary This function handles updating the local state with the latest screen size.
*
* @description
* This function is used as an event handler to handle screen resize. It uses getSizeForWidth
* to get the text equivalent and some nice utility variables from it.
*/
const handleResize = () => {
const size = getSizeForWidth(window.innerWidth);
const isMobile = size === 'xs';
const isTablet = size === 's' || size === 'm';
setResponsive({ size, isMobile, isTablet });
};

/**
* @summary This statement handles the addition and removal for the resize event listener.
*
* @description
* We pass a [] to ensure that this only runs once. This ensures that we add the event
* listener once this starts up, and we then run the handleResize function once manually.
* Once that's done, we go ahead and remove it during "deconstruction", using the return
* statement.
*/
useEffect(() => {
window.addEventListener('resize', handleResize);
handleResize();
Expand Down
10 changes: 10 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import type { AppProps } from 'next/app';
import { Grommet } from 'grommet';
import theme from '../utils/grommet-theme';

/**
* @summary This is a wrapper around the entire app.
*
* @description
* Note that this is where we use Grommet to wrap the entire thing
* in a single theme.
*
* @param {AppProps} props props
* @returns the application.
*/
function MyApp({ Component, pageProps }: AppProps) {
return (
<Grommet theme={theme}>
Expand Down
4 changes: 4 additions & 0 deletions pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import Document, {
Html, Head, Main, NextScript, DocumentContext,
} from 'next/document';

/**
* This is the document tag. Put the fonts and any other external stylesheets here.
* You could basically put anything except the title tag in this section.
*/
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
Expand Down
78 changes: 65 additions & 13 deletions pages/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import useResponsiveContext from '../hooks/use-responsive-context';
import MobileNavbar from '../components/navbar/navbar-mobile';
import { Empty } from '../types/utils';

/**
* This object determines how to split the screen between the "image" and the "text"
* for various screen sizes.
*
* @example
* On a large (l) screen, the image would take 1/3 of the screen width, and the text
* would occupy 2/3 of the screen.
*/
const columnsLayout: Record<string, string[]> = {
xs: ['1'],
s: ['1'],
Expand All @@ -17,6 +25,43 @@ const columnsLayout: Record<string, string[]> = {
xl: ['1/2', '1/2'],
};

/**
* This set of Props just wants to know if we're rendering it on mobile.
*/
type BottomStickingNavbarProps = {
isMobile: boolean;
};

/**
* @summary Component to conditionally render the navbar at the bottom of the page.
*
* @description
* We return nothing if we're not on mobile. Otherwise, we add an empty box right before
* the navbar. The reason we do this is to ensure that the "fixed" navbar does not just
* straight up overlap over the text content.
*
* @param {BottomStickingNavbarProps} props props
* @returns the component.
*/
const BottomStickingNavbar: FC<BottomStickingNavbarProps> = ({ isMobile }) => {
if (!isMobile) return null;
return (
<>
<Box pad="large" />
<MobileNavbar />
</>
);
};

/**
* @summary Component to render the "About" Page.
*
* @description
* Shows an image and a short description about the project. Renders slightly
* differently on different screen sizes.
*
* @returns the component.
*/
const About: NextPage = () => {
const { size, isMobile, isTablet } = useResponsiveContext();
const isSmaller = isMobile || isTablet;
Expand All @@ -33,6 +78,24 @@ const About: NextPage = () => {
vertical: 'xsmall',
};

/**
* @summary Component to render a paragraph.
*
* @description
* The reason this exists within the About Component is that we want to use
* some of the props without having to pass it manually each time. The text itself
* is pretty straightforward.
*
* It uses "Empty" because there's an eslint rule that cries about stuff if we just
* use "{}" because we end up using the children parameter anyway.
*
* @todo
* As part of a larger revamp, we should move away from using FC. This is considered
* to be an antipattern. Explicitly passing children makes more sense.
*
* @param {Empty} props props
* @returns the component.
*/
const AboutParagraph: FC<Empty> = ({ children }) => {
const fontSize = size === 'xl' ? 'xlarge' : 'large';
return (
Expand All @@ -44,19 +107,9 @@ const About: NextPage = () => {
);
};

const BottomStickingNavbar: FC<{}> = () => {
if (!isMobile) return null;
return (
<>
<Box pad="large" />
<MobileNavbar />
</>
);
};

return (
<>
<PageHead title="First Issues" />
<PageHead title="First Issues | About" />
<Navbar />
<Box align="center" justify="center" direction="column" pad="large">
{
Expand All @@ -81,15 +134,14 @@ const About: NextPage = () => {
</AboutParagraph>
<AboutParagraph>
This project is inspired by
{' '}
<Anchor label="Good First Issues" href="http://goodfirstissues.com/" />
, a similar project by Daren Sin.
</AboutParagraph>
</Box>
</Grid>
</Box>
</Box>
<BottomStickingNavbar />
<BottomStickingNavbar isMobile={isMobile} />
</>
);
};
Expand Down
19 changes: 0 additions & 19 deletions pages/api/hello.ts

This file was deleted.

14 changes: 14 additions & 0 deletions pages/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ import createRepositorySearchQuery from '../../services/github/create-query';
import getRepositories from '../../services/github/submit-query';
import { Repository, RepositorySearchResponse } from '../../types/repository-search';

/**
* @summary This function takes search parameters and returns the Github Search API Response.
*
* @description
* We first convert the string request body into a proper JSON Object. Once that's done we
* pass on the data to the @see createRepositorySearchQuery function to get the query, then send
* that over to the GitHub API via @see getRepositories function.
*
* We use the map here instead of cleanly in a separate function because I couldn't figure out how
* to get the type hinting when making a separate function.
*
* @param req request
* @param res response
*/
const handler = async (req: NextApiRequest, res: NextApiResponse<RepositorySearchResponse>) => {
const repoName = JSON.parse(req.body).repo;
const query = createRepositorySearchQuery({ name: repoName });
Expand Down
29 changes: 24 additions & 5 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,34 @@ import MobileNavbar from '../components/navbar/navbar-mobile';
import useRepoData from '../hooks/use-repo-data';
import Viewer from '../components/viewer/viewer';

/**
* @summary Component to render the landing page.
*
* @description
* Shows the search and view components. This internally connects to the useRepoData
* hook, and actually connects to the backend from here. It conditionally renders the
* Viewer component depending on the status of this hook.
*
* It also conditionally renders the navbar on mobile devices.
*
* @returns the component.
*/
const Home: NextPage = () => {
const { isMobile } = useResponsiveContext();
const [searchTerm, setSearchTerm] = useState('');
const { isLoading, data } = useRepoData(searchTerm);

const hijackUpdate = (term: string) => {
setSearchTerm(term);
};

/**
* @summary This function conditionally renders the loader, error or viewer components.
*
* @description
* This function uses the useRepoData hook to figure out what the status of the response is.
* Depending on the response, we either show the loader or the error components (both of which
* don't exist - prompting us to use regular boxes instead). If everything is good we go for
* rendering the Viewer component.
*
* @returns a component.
*/
const renderViewer = () => {
if (isLoading) {
return <Box> Loading! </Box>;
Expand All @@ -42,7 +61,7 @@ const Home: NextPage = () => {
<PageHead title="First Issues" />
<Navbar />
<Box flex pad="large" margin="large">
<SearchForm updateSearchTerm={(term) => hijackUpdate(term)} />
<SearchForm updateSearchTerm={(term) => setSearchTerm(term)} />
</Box>
<Box flex>
{
Expand Down

1 comment on commit 39601d0

@vercel
Copy link

@vercel vercel bot commented on 39601d0 Sep 26, 2021

Choose a reason for hiding this comment

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

Please sign in to comment.