Skip to content

Commit

Permalink
Merge pull request #98 from AkinariHex/develop
Browse files Browse the repository at this point in the history
User friendly error visualization
  • Loading branch information
hburn7 authored Feb 17, 2024
2 parents 6576a5a + f3765cf commit 15e0820
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 55 deletions.
37 changes: 3 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,5 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
![osu! Tournament Rating](https://akinariosu.s-ul.eu/X1Me0Jpd)

## Getting Started
# o!TR Web

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
[![CodeFactor](https://www.codefactor.io/repository/github/osu-tournament-rating/otr-web/badge)](https://www.codefactor.io/repository/github/osu-tournament-rating/otr-web)
4 changes: 4 additions & 0 deletions app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ export async function saveTournamentMatches(
}
}

export async function getOsuModeCookie() {
return cookies().get('OTR-user-selected-osu-mode');
}

export async function changeOsuModeCookie(mode?: string) {
await cookies().set('OTR-user-selected-osu-mode', mode ?? '0');
return;
Expand Down
4 changes: 4 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@
/* USER RATING TIER */
--tier-bar-background: var(--gray-300);
--tier-bar-accent: var(--accent-color);

/* TOAST */
--toast-error-bg: var(--yellow-200);
--toast-error-border: 50, 70%, 64%;
}

* {
Expand Down
16 changes: 10 additions & 6 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Footer from '@/components/Footer/Footer';
import NavBar from '@/components/NavBar/NavBar';
import { LayoutProvider } from '@/components/LayoutProvider/LayoutProvider';
import ErrorProvider from '@/util/ErrorContext';
import UserProvider from '@/util/UserLoggedContext';
import type { Metadata } from 'next';
import { Viewport } from 'next';
Expand Down Expand Up @@ -32,11 +33,14 @@ export default function RootLayout({
return (
<html lang="en" className={inter.variable}>
<body>
<UserProvider>
<NavBar />
{children}
<Footer />
</UserProvider>
<ErrorProvider>
<UserProvider>
<LayoutProvider>
{children}
<Footer />
</LayoutProvider>
</UserProvider>
</ErrorProvider>
</body>
</html>
);
Expand Down
7 changes: 5 additions & 2 deletions app/unauthorized/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@

import LoginButton from '@/components/Button/LoginButton';
import Card from '@/components/Card/Card';
import { useUser } from '@/util/hooks';
import { useSetError, useUser } from '@/util/hooks';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import styles from './page.module.css';

export default function Unauthorized() {
const router = useRouter();
const user = useUser();
const setError = useSetError();

if (user?.osuId) {
return router.push('/');
}

setError(user?.error);

return (
<main className={styles.container}>
<main className={styles.container} style={{ paddingTop: '2vw' }}>
<Card
title="Unauthorized"
description="Currently, the o!TR website is in a closed pre-alpha state. Only whitelisted users are allowed. We will open things up once we have more features implemented. Thanks for your patience!"
Expand Down
50 changes: 50 additions & 0 deletions components/ErrorToast/ErrorToast.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.toast {
width: 26em;
height: auto;
background-color: hsla(var(--toast-error-border));
color: #222;
display: flex;
flex-flow: column;
justify-content: center;
position: fixed;
bottom: 0;
left: 50%;
margin-left: -13em;
border-radius: 0.3rem;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
border: 0.4em solid hsl(var(--toast-error-border));
font-family: var(--font-families);
transform: translateY(-1em);
opacity: 1;
}

.toast .header {
display: flex;
flex-flow: row;
padding: 0.6em 1em 0.7em 1em;
font-weight: 700;
gap: 1.2em;
align-items: center;
}

.toast .header :first-child {
position: relative;
}

.toast .header :first-child::after {
content: '/';
position: absolute;
top: -0.04em;
right: -0.7em;
font-weight: 300;
color: #333;
font-size: 1.1em;
}

.toast .body {
padding: 1em;
background-color: hsla(var(--toast-error-bg), 0.8);
border-radius: 0.2rem;
}
42 changes: 42 additions & 0 deletions components/ErrorToast/ErrorToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { motion } from 'framer-motion';
import styles from './ErrorToast.module.css';

export default function ErrorToast({
status,
text,
message,
}: {
status: number;
text: string;
message: string;
}) {
const initial = {
translateY: '0.5em',
opacity: 0,
};

const animate = {
translateY: '-1em',
opacity: 1,
};

const exit = {
translateY: '1em',
opacity: 0,
};

return (
<motion.div
initial={initial}
animate={animate}
exit={exit}
className={styles.toast}
>
<div className={styles.header}>
<span>{status}</span>
<span>{text}</span>
</div>
<div className={styles.body}>{message}</div>
</motion.div>
);
}
15 changes: 15 additions & 0 deletions components/LayoutProvider/LayoutProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';

// Use usePathname for catching route name.
import { usePathname } from 'next/navigation';
import NavBar from '../NavBar/NavBar';

export const LayoutProvider = ({ children }: { children: React.ReactNode }) => {
const pathname = usePathname();
return (
<>
{pathname !== '/unauthorized' && <NavBar />}
{children}
</>
);
};
20 changes: 15 additions & 5 deletions components/NavBar/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
'use client';

import { getOsuModeCookie } from '@/app/actions';
import moonSVG from '@/public/icons/moon.svg';
import logo from '@/public/logos/small.svg';
import { cookies } from 'next/headers';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import HamburgerMobile from './HamburgerMobile/HamburgerMobile';
import ModeSwitcher from './ModeSwitcher/ModeSwitcher';
import styles from './NavBar.module.css';
import Routes from './Routes/Routes';
import UserLogged from './UserLogged/UserLogged';

export default function NavBar() {
const cookieMode = cookies().get('OTR-user-selected-osu-mode');
const [cookieMode, setCookieMode] = useState({});

useEffect(() => {
const cookie = Promise.resolve(getOsuModeCookie());
cookie.then((value) => {
setCookieMode(value);
});
}, []);

return (
<nav className={styles.navbar}>
Expand All @@ -21,12 +31,12 @@ export default function NavBar() {
<Routes />
{/* <Link href={'/donate'}>Donate</Link> */}
<div className={styles.actions}>
<ModeSwitcher mode={cookieMode?.value} />
{/* <button>
{cookieMode?.value && <ModeSwitcher mode={cookieMode?.value} />}
<button>
<div className={styles.darkModeSwitcher}>
<Image src={moonSVG} alt="Dark Mode Switcher" fill />
</div>
</button> */}
</button>
<UserLogged />
</div>
</div>
Expand Down
14 changes: 6 additions & 8 deletions components/NavBar/Routes/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ export default function Routes() {
Dashboard
</Link>
)}
{user?.osuId && (
<Link
href={'/leaderboards'}
className={pathname === '/leaderboards' ? styles.active : ''}
>
Leaderboards
</Link>
)}
<Link
href={'/leaderboards'}
className={pathname === '/leaderboards' ? styles.active : ''}
>
Leaderboards
</Link>
{user?.osuId && (
<Link
href={'/submit'}
Expand Down
47 changes: 47 additions & 0 deletions util/ErrorContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client';
import { AnimatePresence } from 'framer-motion';
import { createContext, useEffect, useMemo, useState } from 'react';

import ErrorToast from '@/components/ErrorToast/ErrorToast';

import type { ReactNode } from 'react';

export const ErrorContext = createContext<object | undefined>(undefined);

export const SetErrorContext = createContext<object | undefined>(undefined);

type Props = {
children: ReactNode;
};

export default function ErrorProvider({ children }: Props): JSX.Element {
const [error, setError] = useState<object | undefined>();
const [show, setShow] = useState(false);

useEffect(() => {
if (!error || show) return;

setShow(true);
setTimeout(() => {
setError(undefined);
setShow(false);
}, 6000);
}, [error, show]);

return (
<SetErrorContext.Provider value={useMemo(() => setError, [setError])}>
<ErrorContext.Provider value={useMemo(() => error, [error])}>
<AnimatePresence>
{error && (
<ErrorToast
message={error?.message}
status={error?.status}
text={error?.text}
/>
)}
</AnimatePresence>
{children}
</ErrorContext.Provider>
</SetErrorContext.Provider>
);
}
9 changes: 9 additions & 0 deletions util/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { useContext } from 'react';
import { ErrorContext, SetErrorContext } from './ErrorContext';
import { UserLoggedContext } from './UserLoggedContext';

export function useUser() {
return useContext(UserLoggedContext);
}

export function useError() {
return useContext(ErrorContext);
}

export function useSetError() {
return useContext(SetErrorContext);
}

0 comments on commit 15e0820

Please sign in to comment.