Skip to content

Commit

Permalink
Merge pull request #10 from williamquintas/feature/#9-migrate-project…
Browse files Browse the repository at this point in the history
…-to-use-nextjs

#9: Migrate project to use Next.js
  • Loading branch information
williamquintas authored May 30, 2022
2 parents f95f1a2 + 3c36049 commit cf2b5f0
Show file tree
Hide file tree
Showing 37 changed files with 699 additions and 210 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
REACT_APP_GOOGLE_API_KEY = "string"
NEXT_PUBLIC_GOOGLE_API_KEY = "string"
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
/.pnp
.pnp.js

# next.js
/.next/
/out/

# testing
/coverage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
SelectChangeEvent,
} from "@mui/material";
import { FunctionComponent } from "react";
import "./ColorPicker.css";

export const Colors = {
Gray: "#212529",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ListItemText,
} from "@mui/material";
import { Fragment, FunctionComponent, useState } from "react";
import { IFile } from "../App/App";
import { IFile } from "../../pages/_app";

interface CoordinatesListProps {
file: IFile;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { InsertDriveFile } from "@mui/icons-material";
import Button from "@mui/material/Button";
import { ChangeEvent, Fragment, FunctionComponent } from "react";
import "./FileUploadButton.css";

interface IFileUploadButtonProps {
onSelectFile: (evt: ChangeEvent<HTMLInputElement>) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import {
} from "@mui/material";
import Papa from "papaparse";
import { ChangeEvent, FunctionComponent } from "react";
import { Link } from "react-router-dom";
import { IFile } from "../App/App";
import { useAppDispatch, useAppSelector } from "../../config/hooks";
import {
add,
changeColor,
remove,
selectFiles,
} from "../../features/files/slice";
import ColorPicker, { Colors } from "../ColorPicker/ColorPicker";
import FileUploadButton from "../FileUploadButton/FileUploadButton";
import "./FilesSelectionContainer.css";
import Link from "../Link/Link";

interface FilesSelectionContainerProps {
files: IFile[];
setFiles: (files: IFile[]) => void;
}
const FilesSelectionContainer: FunctionComponent = () => {
const dispatch = useAppDispatch();
const files = useAppSelector(selectFiles);

const FilesSelectionContainer: FunctionComponent<
FilesSelectionContainerProps
> = ({ files, setFiles }) => {
const getRandomColor = (): string => {
const colors = Object.keys(Colors);
const index = Math.floor(Math.random() * colors.length) + 1;
Expand All @@ -40,13 +41,13 @@ const FilesSelectionContainer: FunctionComponent<
header: true,
skipEmptyLines: true,
complete: ({ data }) => {
const filesList = [...files];
filesList.push({
filename: file.name,
color: getRandomColor(),
data,
});
setFiles(filesList);
dispatch(
add({
filename: file.name,
color: getRandomColor(),
data,
})
);
},
error: (error) => console.error(error),
});
Expand All @@ -68,9 +69,7 @@ const FilesSelectionContainer: FunctionComponent<
};

const onRemoveFile = (index: number) => {
const filesList = [...files];
filesList.splice(index, 1);
setFiles(filesList);
dispatch(remove(index));
};

const onChangeColor = (index: number, evt: SelectChangeEvent) => {
Expand All @@ -80,9 +79,7 @@ const FilesSelectionContainer: FunctionComponent<
return;
}

const filesList = [...files];
filesList[index].color = selectedColor;
setFiles(filesList);
dispatch(changeColor({ index, color: selectedColor }));
};

return (
Expand All @@ -100,7 +97,7 @@ const FilesSelectionContainer: FunctionComponent<
/>
)}
{files.length > 0 && (
<Link to="/map">
<Link href="/map">
<Button variant="contained" size="small" endIcon={<Send />}>
{" "}
View paths on map{" "}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Box, Link } from "@mui/material";
import { FunctionComponent } from "react";
import "./Footer.css";

const Footer: FunctionComponent = () => (
<Box component="footer">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AppBar, Toolbar, Typography } from "@mui/material";
import { FunctionComponent } from "react";
import LogoIcon from "../Logo/Logo";
import "./Header.css";

const Header: FunctionComponent = () => (
<div>
Expand Down
123 changes: 123 additions & 0 deletions components/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import MuiLink, { LinkProps as MuiLinkProps } from "@mui/material/Link";
import { styled } from "@mui/material/styles";
import clsx from "clsx";
import NextLink, { LinkProps as NextLinkProps } from "next/link";
import { useRouter } from "next/router";
import * as React from "react";

// Add support for the sx prop for consistency with the other branches.
const Anchor = styled("a")({});

interface NextLinkComposedProps
extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href">,
Omit<NextLinkProps, "href" | "as" | "onClick" | "onMouseEnter"> {
to: NextLinkProps["href"];
linkAs?: NextLinkProps["as"];
}

export const NextLinkComposed = React.forwardRef<
HTMLAnchorElement,
NextLinkComposedProps
>(function NextLinkComposed(props, ref) {
const { to, linkAs, replace, scroll, shallow, prefetch, locale, ...other } =
props;

return (
<NextLink
href={to}
prefetch={prefetch}
as={linkAs}
replace={replace}
scroll={scroll}
shallow={shallow}
passHref
locale={locale}
>
<Anchor ref={ref} {...other} />
</NextLink>
);
});

export type LinkProps = {
activeClassName?: string;
as?: NextLinkProps["as"];
href: NextLinkProps["href"];
linkAs?: NextLinkProps["as"]; // Useful when the as prop is shallow by styled().
noLinkStyle?: boolean;
} & Omit<NextLinkComposedProps, "to" | "linkAs" | "href"> &
Omit<MuiLinkProps, "href">;

// A styled version of the Next.js Link component:
// https://nextjs.org/docs/api-reference/next/link
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(function Link(
props,
ref
) {
const {
activeClassName = "active",
as,
className: classNameProps,
href,
linkAs: linkAsProp,
locale,
noLinkStyle,
prefetch,
replace,
role, // Link don't have roles.
scroll,
shallow,
...other
} = props;

const router = useRouter();
const pathname = typeof href === "string" ? href : href.pathname;
const className = clsx(classNameProps, {
[activeClassName]: router.pathname === pathname && activeClassName,
});

const isExternal =
typeof href === "string" &&
(href.indexOf("http") === 0 || href.indexOf("mailto:") === 0);

if (isExternal) {
if (noLinkStyle) {
return <Anchor className={className} href={href} ref={ref} {...other} />;
}

return <MuiLink className={className} href={href} ref={ref} {...other} />;
}

const linkAs = linkAsProp || as;
const nextjsProps = {
to: href,
linkAs,
replace,
scroll,
shallow,
prefetch,
locale,
};

if (noLinkStyle) {
return (
<NextLinkComposed
className={className}
ref={ref}
{...nextjsProps}
{...other}
/>
);
}

return (
<MuiLink
component={NextLinkComposed}
className={className}
ref={ref}
{...nextjsProps}
{...other}
/>
);
});

export default Link;
File renamed without changes.
9 changes: 4 additions & 5 deletions src/components/Map/Map.tsx → components/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Wrapper } from "@googlemaps/react-wrapper";
import { FunctionComponent, useEffect, useRef, useState } from "react";
import { IFile } from "../App/App";
import "./Map.css";
import { IFile } from "../../pages/_app";

interface MapContainerProps {
center: google.maps.LatLngLiteral;
Expand Down Expand Up @@ -61,18 +60,18 @@ interface MapProps {
}

const Map: FunctionComponent<MapProps> = ({ files }) => {
const { REACT_APP_GOOGLE_API_KEY } = process.env;
const NEXT_PUBLIC_GOOGLE_API_KEY = process.env.NEXT_PUBLIC_GOOGLE_API_KEY;
const center = {
lat: Number(files[0]?.data?.[0].latitude),
lng: Number(files[0]?.data?.[0].longitude),
};

if (!REACT_APP_GOOGLE_API_KEY) {
if (!NEXT_PUBLIC_GOOGLE_API_KEY) {
throw new Error("No Google Maps API Key specified.");
}

return (
<Wrapper apiKey={REACT_APP_GOOGLE_API_KEY}>
<Wrapper apiKey={NEXT_PUBLIC_GOOGLE_API_KEY}>
<MapContainer center={center} zoom={14} files={files} />
</Wrapper>
);
Expand Down
6 changes: 6 additions & 0 deletions config/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { AppDispatch, AppState } from "./store";

export const useAppDispatch = () => useDispatch<AppDispatch>();

export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
23 changes: 23 additions & 0 deletions config/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Action, configureStore, ThunkAction } from "@reduxjs/toolkit";
import filesReducer from "../features/files/slice";

export function makeStore() {
return configureStore({
reducer: { files: filesReducer },
});
}

const store = makeStore();

export type AppState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
Action<string>
>;

export default store;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions css/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
43 changes: 43 additions & 0 deletions features/files/slice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import type { AppState } from "../../config/store";
import { IFile } from "../../pages/_app";

export interface FilesState {
files: IFile[];
}

const initialState: FilesState = {
files: [],
};

export const filesSlice = createSlice({
name: "files",
initialState,
reducers: {
add: (state, action: PayloadAction<IFile>) => {
const currentFiles = [...state.files];
currentFiles.push(action.payload);
state.files = currentFiles;
},
remove: (state, action: PayloadAction<number>) => {
const currentFiles = [...state.files];
currentFiles.splice(action.payload);
state.files = currentFiles;
},
changeColor: (
state,
action: PayloadAction<{ index: number; color: string }>
) => {
const currentFiles = [...state.files];
const { index, color } = action.payload;
currentFiles[index].color = color;
state.files = currentFiles;
},
},
});

export const { add, remove, changeColor } = filesSlice.actions;

export const selectFiles = (state: AppState) => state.files.files;

export default filesSlice.reducer;
5 changes: 5 additions & 0 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
6 changes: 6 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}

module.exports = nextConfig
Loading

0 comments on commit cf2b5f0

Please sign in to comment.