Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring for separation of concerns #26

Merged
merged 29 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
57a868c
good bye
test-user-jinma-ml Dec 9, 2021
5a53bd0
initialize with vite
test-user-jinma-ml Dec 9, 2021
e258b82
add simple pages
test-user-jinma-ml Dec 9, 2021
51de226
introduce msw
test-user-jinma-ml Dec 10, 2021
cf8fbf4
adjust mock api response
test-user-jinma-ml Dec 13, 2021
d5668bd
rename to http
test-user-jinma-ml Dec 13, 2021
a7e6255
implement auth hook
test-user-jinma-ml Dec 13, 2021
4e75e63
adapting to the existing style
test-user-jinma-ml Dec 15, 2021
6ea1cc5
adjusting layouts with routes
test-user-jinma-ml Dec 15, 2021
6d695f8
add delay to mock response
test-user-jinma-ml Dec 15, 2021
842067f
render error message
test-user-jinma-ml Dec 15, 2021
b85d330
npx npm-check-updates -u
jinmayamashita Jan 14, 2022
415e4c0
example useMutation hook
jinmayamashita Jan 14, 2022
11b92de
update user type
jinmayamashita Jan 14, 2022
7172283
try use constate
jinmayamashita Jan 14, 2022
b29e2dc
add separate components
jinmayamashita Jan 14, 2022
0786075
add storybook
jinmayamashita Jan 14, 2022
e8b699a
try testing button with storybook
jinmayamashita Jan 17, 2022
03b7b25
introduce i18next
jinmayamashita Jan 17, 2022
a520ee9
specify the version of node
jinmayamashita Jan 17, 2022
d3f4fba
add type check script
jinmayamashita Jan 17, 2022
06908af
adjust demo styles
jinmayamashita Jan 17, 2022
d7acb04
count component to simply
jinmayamashita Jan 17, 2022
dc95587
use lazy loads
jinmayamashita Jan 17, 2022
fd316ca
try to zustand
jinmayamashita Mar 2, 2022
91c4b73
use css modules with classNames
jinmayamashita Mar 2, 2022
5f94e94
Update __mocks__/api/login.ts
jinmayamashita Mar 2, 2022
08f9c22
Update src/pages/NotFound.tsx
jinmayamashita Mar 2, 2022
544abba
Update src/routes.tsx
jinmayamashita Mar 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions __mocks__/api/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { rest } from "../rest";
export const login = [
rest.post("/login", (_, res, ctx) =>
res(
// Delays response for 2000ms.
jinmayamashita marked this conversation as resolved.
Show resolved Hide resolved
ctx.delay(1000),
ctx.json({
token: "QpwL5tke4Pnpja7X4",
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@types/react-dom": "17.0.11",
"@vitejs/plugin-react": "1.1.1",
"msw": "0.36.3",
"sass": "1.45.0",
"typescript": "4.5.2",
"vite": "2.7.1"
},
Expand Down
7 changes: 7 additions & 0 deletions src/components/layouts/BlankLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PropsWithChildren } from "react";

type Props = PropsWithChildren<{}>;
yukinoda marked this conversation as resolved.
Show resolved Hide resolved

export const BlankLayout = ({ children }: Props) => {
return <div>{children}</div>;
};
40 changes: 23 additions & 17 deletions src/components/layouts/HeaderLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { ReactNode } from "react";
import { useAuth } from "@/hooks/useAuth";
import { PropsWithChildren } from "react";
import { Link } from "wouter";

type Props = {
children: ReactNode;
type Props = PropsWithChildren<{}>;
export const HeaderLayout = ({ children }: Props) => {
const { isLoggedIn, logout } = useAuth();
return (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/profile">Profile</Link>
</li>
{isLoggedIn ? (
<li>
<button onClick={logout}>Logout</button>
</li>
) : null}
</ul>
<div>{children}</div>
</div>
);
};
export const HeaderLayout = ({ children }: Props) => (
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>

<li>
<Link to="/profile">Profile</Link>
</li>
</ul>
<div>{children}</div>
</div>
);
12 changes: 12 additions & 0 deletions src/components/layouts/SideLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PropsWithChildren } from "react";

type Props = PropsWithChildren<{}>;

export const SideLayout = ({ children }: Props) => {
return (
<div>
<div>side</div>
{children}
</div>
);
};
26 changes: 25 additions & 1 deletion src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
PropsWithChildren,
useCallback,
useContext,
useState,
} from "react";
import { useLocation } from "wouter";
import { AuthRequest, AuthResponse } from "@/types/auth";
import { useMutation, useQueryClient } from "react-query";
import { http } from "@/utils/http";
Expand All @@ -15,14 +17,21 @@ type AuthContextData = {
isLoading?: boolean;
};
logout(): void;
isLoggedIn: boolean;
};

const AuthContext = createContext<AuthContextData>({
async login() {},
logout() {},
isLoggedIn: false,
});

export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
const [_, setLocation] = useLocation();
const [isLoggedIn, setIsLoggedIn] = useState(
// TODO: We should validate a access token via request to backend
() => !!localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY)
);
const queryClient = useQueryClient();

const { mutateAsync: authenticateAsync, isLoading } = useMutation(
Expand All @@ -33,7 +42,17 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
const login = useCallback(
async (authRequest: AuthRequest) => {
const data = await authenticateAsync(authRequest);

// Set token to local storage
localStorage.setItem(LOCAL_STORAGE_TOKEN_KEY, data.token);
setIsLoggedIn(true);

// e.g http://localhost:3000/search?foo=bar&baz=99
const params = new URL(document.location.href).searchParams;
const from = params.get("redirect");
params.delete("redirect");

from ? setLocation(`${from}?${params.toString()}`) : setLocation("/home");
},
[authenticateAsync]
) as AuthContextData["login"];
Expand All @@ -43,10 +62,15 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
const logout = useCallback(() => {
localStorage.removeItem(LOCAL_STORAGE_TOKEN_KEY);
queryClient.clear();
setIsLoggedIn(false);

// TODO: Should we need to worry about the search params when user logout?
// e.g /login?redirect=/page?foo=bar&baz=123
setLocation("/login");
}, [queryClient]);

return (
<AuthContext.Provider value={{ login, logout }}>
<AuthContext.Provider value={{ login, logout, isLoggedIn }}>
{children}
</AuthContext.Provider>
);
Expand Down
13 changes: 13 additions & 0 deletions src/main.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
1 change: 1 addition & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StrictMode } from "react";
import { render } from "react-dom";
import { App } from "@/App";
import "./main.scss";

const prepare = async (): Promise<void> => {
if (import.meta.env.DEV && !import.meta.env.VITE_REACT_APP_API_HOST) {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const Home = () => <>Home</>;
export const Home = () => <>HomeScreen</>;
7 changes: 5 additions & 2 deletions src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useAuth } from "@/hooks/useAuth";
import { Redirect } from "wouter";

export const Login = () => {
const { login } = useAuth();
const { isLoggedIn, login } = useAuth();

const onClick = () =>
login({
email: "[email protected]",
password: "cityslicka",
});

return (
return isLoggedIn ? (
<Redirect to="/home" />
) : (
<div>
<div>LoginScreen</div>
<button onClick={onClick} disabled={login.isLoading}>
Expand Down
14 changes: 13 additions & 1 deletion src/pages/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
export const NotFound = () => <>Not Found</>;
import { Link } from "wouter";

export const NotFound = () => (
<>
<div>
<h1>404 - Not found</h1>
<p>The page you were looking for was not found.</p>
<Link href="/">
<button>Go to home</button>
</Link>
</div>
</>
jinmayamashita marked this conversation as resolved.
Show resolved Hide resolved
);
7 changes: 4 additions & 3 deletions src/pages/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { useQuery } from "react-query";
import { useQuery, QueryCache } from "react-query";
import { http } from "@/utils/http";
import { User } from "@/types/user";

const useGetUserQuery = (id: number) =>
const useGetUserQuery = ({ id }: { id: string }) =>
useQuery("user", async () => await http.get<User>(`/user/${id}`));

export const Profile = () => {
const { isLoading, data, isFetched } = useGetUserQuery(1);
const { isLoading, data, isFetched, isError } = useGetUserQuery({ id: "1" });

return (
<div>
<div>ProfileScreen</div>
<div>{isLoading && "Loading..."}</div>
<div>{isError && "Failed to fetch"}</div>
{isFetched && data ? (
<div>
<div>
Expand Down
88 changes: 57 additions & 31 deletions src/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,70 @@
import { PropsWithChildren } from "react";
import { Redirect, Route, Switch } from "wouter";
import { ComponentProps } from "react";
import { Route as _Route, Redirect, Switch, useLocation } from "wouter";
import { Home } from "@/pages/Home";
import { Login } from "@/pages/Login";
import { Profile } from "@/pages/Profile";
import { NotFound } from "@/pages/NotFound";
import { useAuth } from "@/hooks/useAuth";
import { HeaderLayout } from "@/components/layouts/HeaderLayout";
import { LOCAL_STORAGE_TOKEN_KEY } from "@/constants/localStorage";
import { SideLayout } from "@/components/layouts/SideLayout";
import { BlankLayout } from "@/components/layouts/BlankLayout";
import { Path } from "@/types/path";

// TODO
jinmayamashita marked this conversation as resolved.
Show resolved Hide resolved
// RestrictAccess.tsx

const PrivateRoute = ({
path,
children,
}: PropsWithChildren<{ path: string }>) => {
// TODO should we validate a access token on backend?
const hasToken = localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY);
const renderChildren = () => (hasToken ? children : <Redirect to="/login" />);
// page layouts
const blankLayout: Path[] = ["/login"];
const sidebarLayout: Path[] = [];

const getPageLayout = (path: Path) => {
if (blankLayout.includes(path)) return BlankLayout;
if (sidebarLayout.includes(path)) return SideLayout;

return HeaderLayout;
};

// routes
type RouteProps = ComponentProps<typeof _Route> & { path?: Path };
const Route = ({ children, path }: RouteProps) => {
return <_Route path={path}>{children}</_Route>;
};

const PrivateRoute = ({ path, children }: RouteProps) => {
const { isLoggedIn } = useAuth();

const params = new URL(document.location.href).searchParams.toString();
const qs = params ? `?redirect=${path}&${params}` : `?redirect=${path}`;

const renderChildren = () =>
isLoggedIn ? children : <Redirect to={`/login${qs}`} />;

return <Route path={path}>{renderChildren}</Route>;
};

export const Routes = () => (
<HeaderLayout>
<Switch>
<Route path="/">
<Redirect to={"home"} />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/home">
<Home />
</Route>
<PrivateRoute path="/profile">
<Profile />
</PrivateRoute>
<Route>
<NotFound />
</Route>
</Switch>
</HeaderLayout>
);
export const Routes = () => {
const [location] = useLocation();
const Layout = getPageLayout(location as Path);

return (
<Layout>
<Switch>
<Route path="/">
<Redirect to={"home"} />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/home">
<Home />
</Route>
<PrivateRoute path="/profile">
<Profile />
</PrivateRoute>
<Route>
<NotFound />
</Route>
</Switch>
</Layout>
);
};
2 changes: 2 additions & 0 deletions src/types/path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// It looks like we don't need to use enum.
yukinoda marked this conversation as resolved.
Show resolved Hide resolved
export type Path = "/" | "/login" | "/home" | "/profile";
18 changes: 16 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==

chokidar@^3.4.2:
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
Expand Down Expand Up @@ -880,6 +880,11 @@ ieee754@^1.1.13:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==

immutable@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==

inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
Expand Down Expand Up @@ -1308,6 +1313,15 @@ safe-buffer@~5.2.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==

[email protected]:
version "1.45.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.0.tgz#192ede1908324bb293a3e403d1841dbcaafdd323"
integrity sha512-ONy5bjppoohtNkFJRqdz1gscXamMzN3wQy1YH9qO2FiNpgjLhpz/IPRGg0PpCjyz/pWfCOaNEaiEGCcjOFAjqw==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"

scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
Expand All @@ -1331,7 +1345,7 @@ signal-exit@^3.0.2:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af"
integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==

source-map-js@^1.0.1:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
Expand Down