Skip to content

Commit

Permalink
Merge pull request #84 from shanedg/fun-with-html-tables
Browse files Browse the repository at this point in the history
Scaffold world screens with static, dummy data
  • Loading branch information
shanedg committed Jul 8, 2023
2 parents 1d6016a + 18cc6a9 commit 992d4b0
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 23 deletions.
2 changes: 2 additions & 0 deletions client/__mocks__/styleMock.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// https://jestjs.io/docs/webpack#handling-static-assets
module.exports = {};
2 changes: 2 additions & 0 deletions client/__snapshots__/webpack.config.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`webpackConfig development mode matches snapshot 1`] = `
{
"devServer": {
"historyApiFallback": true,
"setupMiddlewares": [Function],
},
"devtool": "eval-source-map",
Expand Down Expand Up @@ -123,6 +124,7 @@ exports[`webpackConfig development mode matches snapshot 1`] = `
exports[`webpackConfig production mode matches snapshot 1`] = `
{
"devServer": {
"historyApiFallback": true,
"setupMiddlewares": [Function],
},
"devtool": "source-map",
Expand Down
4 changes: 4 additions & 0 deletions client/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ export default {
collectCoverage: true,
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
moduleNameMapper: {
// See https://jestjs.io/docs/webpack#handling-static-assets
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.cjs',
},
};
2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@types/jest": "~29.5.2",
"@types/node": "~16.18.3",
"@types/react-dom": "~18.2.6",
"@types/react-router-dom": "~5.3.3",
"@types/react": "~18.2.14",
"@types/testing-library__jest-dom": "~5.14.5",
"@typescript-eslint/eslint-plugin": "~5.60.0",
Expand Down Expand Up @@ -95,6 +96,7 @@
"axios": "~1.4.0",
"core-js": "~3.31.0",
"react-dom": "~18.2.0",
"react-router-dom": "~6.14.0",
"react": "~18.2.0"
}
}
3 changes: 3 additions & 0 deletions client/src/App/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.navigation-list {
list-style-type: none;
}
179 changes: 158 additions & 21 deletions client/src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { StrictMode } from 'react';
import React, { StrictMode, useEffect, useState } from 'react';
import { createBrowserRouter, RouterProvider, Link, useParams, Outlet } from 'react-router-dom';

import { Nav } from './Nav';
import './App.css';
import { useGuildMemberData } from './use-guild-member-data';
import { Welcome } from './Welcome';

type GuildUserData = ReturnType<typeof useGuildMemberData>
type GuildUserData = ReturnType<typeof useGuildMemberData>;

const getWelcomeMessage = (guildUser: GuildUserData) => {
const welcome = 'welcome to the trash compactor';
Expand All @@ -16,29 +17,165 @@ const getWelcomeMessage = (guildUser: GuildUserData) => {
return `${welcome}, <unknown>`;
};

const App = () => {
const DelayedLoadingMessage = () => {
const [showLoading, setShowLoading] = useState(false);

useEffect(() => {
// Showing a loading message right away contributes to worse perceived performance
const timeout = setTimeout(() => setShowLoading(true), 500);

return () => clearTimeout(timeout);
});

if (showLoading) {
return (<p>loading...</p>);
}

return null;
};

const Home = () => {
const guildUser = useGuildMemberData();

if (guildUser) {
return (
<>
<ul className="navigation-list">
<li>
<Link to="/new">new</Link>
</li>
<li>
<Link to="/worlds">worlds</Link>
</li>
</ul>
<Welcome message={getWelcomeMessage(guildUser)} />
</>
);
}

return (<DelayedLoadingMessage />);
};

const WorldsList = () => {
const fakeWorlds = [
{
id: 1,
label: 'world one',
version: '1.16.5',
createdAt: '2023/06/28',
lastOnline: '2023/06/28',
createdBy: '@shaned.gg'
},
{
id: 2,
label: 'world two',
version: '1.19.0',
createdAt: '2023/06/28',
lastOnline: '2023/06/28',
createdBy: '@shaned.gg'
},
{
id: 3,
label: 'world three',
version: '1.20.1',
createdAt: '2023/06/28',
lastOnline: '2023/06/28',
createdBy: '@shaned.gg'
},
];

return (
<>
<ul className="navigation-list">
<li>
<Link to="/">back</Link>
</li>
</ul>
<table>
<thead>
<tr>
<td>name</td>
<td>version</td>
<td>created</td>
<td>last online</td>
<td>created by</td>
</tr>
</thead>
<tbody>
{fakeWorlds.map(({ id, label, version, createdAt, createdBy, lastOnline }) => {
return (
<tr key={`${id}-${label}-${version}`}>
<td><Link to={`${id}`}>{label}</Link></td>
<td>{version}</td>
<td>{new Date(createdAt).toLocaleDateString()}</td>
<td>{new Date(lastOnline).toLocaleDateString()}</td>
<td>{createdBy}</td>
</tr>
);
})}
</tbody>
</table>
<Outlet />
</>
);
};

const WorldDetail = () => {
const { worldId } = useParams();
return (
<>
<h2>{worldId}</h2>
</>
);
};

const NewWorld = () => {
return (
<>
<ul className="navigation-list">
<li>
<Link to="/">back</Link>
</li>
</ul>
<form>
<label htmlFor="version">version</label>
<select name="version">
<option>1.20.1</option>
</select>
<label htmlFor="name">name</label>
<input name="name" />
<button>create</button>
</form>
</>
);
};

const router = createBrowserRouter([
{
path: '/',
element: <Home />,
},
{
path: '/worlds',
element: <WorldsList />,
children: [
{
path: ':worldId',
element: <WorldDetail />,
},
],
},
{
path: '/new',
element: <NewWorld />,
},
]);

const App = () => {
return (
<StrictMode>
<h1>trshcmpctr</h1>
{guildUser ?
<Welcome message={getWelcomeMessage(guildUser)} /> :
<p>loading ...</p>
}
<Nav links={[
{
href: '#one',
label: 'one'
},
{
href: '#two',
},
{
href: '#three',
label: 'three'
},
]} />
<RouterProvider router={router} />
</StrictMode>
);
};
Expand Down
24 changes: 23 additions & 1 deletion client/src/App/use-guild-member-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,39 @@ import { useEffect, useState } from 'react';

import type { APIGuildMember } from 'discord-api-types/v10';

const cache = {} as { guildMember?: APIGuildMember };
let isFetching = false;

/**
* Custom hook for fetching guild membership data from the authorized endpoint
* Caches the returned membership data for the length of the current session
* @returns APIGuildMember | undefined
*/
export const useGuildMemberData = () => {
const [guildMember, setGuildMember] = useState<APIGuildMember>();

useEffect(() => {
// Avoid triggering duplicate fetch requests between renders
if (isFetching) {
return;
}

isFetching = true;

if (cache.guildMember) {
setGuildMember(cache.guildMember);
isFetching = false;
return;
}

axios.get<APIGuildMember>('/api/v1/authorized')
.then(response => setGuildMember(response.data))
.then(response => {
cache.guildMember = response.data;
setGuildMember(response.data);
isFetching = false;
})
.catch((cause: unknown) => {
isFetching = false;
throw new Error(
'Something went wrong fetching guild membership data',
{ cause }
Expand Down
2 changes: 2 additions & 0 deletions client/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export default (env = {}, argv = {}) => {

return middlewares;
},

historyApiFallback: true
},

devtool: isProduction ? 'source-map' : 'eval-source-map',
Expand Down
Loading

0 comments on commit 992d4b0

Please sign in to comment.