Skip to content

Commit

Permalink
feat: wip and todos
Browse files Browse the repository at this point in the history
  • Loading branch information
shanedg committed May 28, 2024
1 parent ae2329c commit cb38115
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 215 deletions.
1 change: 1 addition & 0 deletions client/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# TODO: FIXME: upload screenshots (videos??) to github build artifacts for debugging???
cypress/screenshots
dist
2 changes: 2 additions & 0 deletions client/cypress/e2e/spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ describe('client', () => {
cy.visit('/');
cy.get('#root h1')
.should('have.text', 'trshcmpctr');

// TODO: FIXME: add more :3
});
});
177 changes: 25 additions & 152 deletions client/src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,164 +1,23 @@
import React, { StrictMode, useEffect, useState } from 'react';
import { createBrowserRouter, RouterProvider, Link, useParams, Outlet } from 'react-router-dom';
import React, { StrictMode, Suspense } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

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

type GuildUserData = ReturnType<typeof useGuildMemberData>;

const getWelcomeMessage = (guildUser: GuildUserData) => {
const welcome = 'welcome to the trash compactor';

if (guildUser?.user?.username) {
return `${welcome}, ${guildUser.user.username}`;
}

return `${welcome}, <unknown>`;
};

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();

// FIXME: if not a guild member, user just sees 'loading...' forever
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>
</>
);
};
import { ErrorCard } from './components/ErrorCard';
import { Home } from './components/Home';
import { NewWorld } from './components/NewWorld';
import { WorldDetail } from './components/WorldDetail';
import { Worlds } from './components/Worlds';

const router = createBrowserRouter([
{
path: '/',
element: <Home />,
errorElement: <ErrorCard error={Error('Problem rendering the route')} />,
},
{
path: '/worlds',
element: <WorldsList />,
element: <Worlds />,
children: [
{
path: ':worldId',
Expand All @@ -175,8 +34,22 @@ const router = createBrowserRouter([
export const App = () => {
return (
<StrictMode>
<h1>trshcmpctr</h1>
<RouterProvider router={router} />
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}>
<article>
<header>
<h1>trshcmpctr</h1>
</header>
<section>
<Suspense fallback={null}>
<RouterProvider router={router} />
</Suspense>
</section>
</article>
</div>
</StrictMode>
);
};
21 changes: 21 additions & 0 deletions client/src/App/components/ErrorCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { type PropsWithChildren } from 'react';

type ErrorCardProps = PropsWithChildren<{
error?: Error;
}>

export const ErrorCard = ({ children, error }: ErrorCardProps) => {
if (!error) {
return <p>oops!</p>;
}

return (
<>
<p>oops!</p>
<p>name: <code>{error.name}</code></p>
<p>message: <code>{error.message}</code></p>
{/* TODO: add error.cause? */}
{children && <>{children}</>}
</>
);
};
60 changes: 60 additions & 0 deletions client/src/App/components/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { type APIGuildMember } from 'discord-api-types/v10';
import React from 'react';
import { Link } from 'react-router-dom';

import { ErrorCard } from './ErrorCard';
import { LoadingContent } from './LoadingContent';
import { Welcome } from './Welcome';
import { useRequest } from '../hooks/use-request';

/**
* TODO:
* @param guildUser TODO:
* @returns TODO:
*/
const getWelcomeMessage = (guildUser: APIGuildMember) => {
const welcome = 'welcome to the trash compactor';

if (guildUser?.user?.username) {
return `${welcome}, ${guildUser.user.username}`;
}

return `${welcome}, <unknown>`;
};

/**
* The home page
*/
export const Home = () => {
const useAuthorizedGuildMemberData = useRequest<APIGuildMember>('/api/v1/authorized');
const { data: guildUser, error, isLoading } = useAuthorizedGuildMemberData();

if (isLoading) {
return (<LoadingContent />);
}

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)} />
</>
);
}

if (error) {
return <ErrorCard error={error} />;
}

// TODO: FIXME: this _should_ never happen based on the states returnable from useAuthorizedGuildMemberData()
// OR is this potentially an "authenticated but not authorized" handler?
// return <ErrorCard error={Error('Loading finished without receiving data or throwing an error')} />;
return <></>;
};
26 changes: 26 additions & 0 deletions client/src/App/components/LoadingContent.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { render, screen } from '@testing-library/react';
import React from 'react';

import { LoadingContent } from './LoadingContent';
import { useHasTimePassed } from '../hooks/use-has-time-passed';

jest.mock('../hooks/use-has-time-passed');

describe('LoadingContent', () => {
beforeEach(() => {
(useHasTimePassed as jest.Mock).mockReset();
});

// TODO: FIXME: renders nothing before time has passed
it('renders', () => {
(useHasTimePassed as jest.Mock).mockReturnValue(false);
expect(render(<LoadingContent />)).toBeTruthy();
});

// TODO: FIXME: renders loading message after time has passed
it('renders 2', () => {
(useHasTimePassed as jest.Mock).mockReturnValue(true);
expect(render(<LoadingContent />)).toBeTruthy();
expect(screen.getByText('l o a d i n g . . .')).toBeTruthy();
});
});
33 changes: 33 additions & 0 deletions client/src/App/components/LoadingContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { type PropsWithChildren } from 'react';

import { useHasTimePassed } from '../hooks/use-has-time-passed';

/**
* Default loading message
*/
const GenericLoadingMessage = () => (
<>
<code>l o a d i n g . . .</code>
</>
);

type LoadingContentProps = PropsWithChildren<{
duration?: number;
}>

/**
* A component that renders a loading message
*/
export const LoadingContent = ({ children, duration = 600 }: LoadingContentProps) => {
// Wait a moment, content _might_ load quickly
// Showing a loading message instantly can contribute to worse perceived performance (Citation needed)
const showLoading = useHasTimePassed(duration);

if (showLoading) {
return children ?
children :
<GenericLoadingMessage />;
}

return <></>;
};
23 changes: 23 additions & 0 deletions client/src/App/components/NewWorld.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Link } from 'react-router-dom';

export 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>
</>
);
};
11 changes: 11 additions & 0 deletions client/src/App/components/WorldDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { useParams } from 'react-router-dom';

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

0 comments on commit cb38115

Please sign in to comment.