Unverified Commit 6747ee74 authored by Michal Čaniga's avatar Michal Čaniga
Browse files

Add leaderboard, home, auth form first versions

parent 835c8c85
This diff is collapsed.
......@@ -4,12 +4,18 @@
"private": true,
"dependencies": {
"@ant-design/icons": "^4.7.0",
"@hookform/error-message": "^2.0.0",
"@hookform/resolvers": "^2.8.3",
"antd": "^4.16.13",
"classnames": "^2.3.1",
"firebase": "^9.2.0",
"firebase-admin": "^10.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hook-form": "^7.19.1",
"react-router-dom": "^5.3.0",
"react-scripts": "4.0.3"
"react-scripts": "4.0.3",
"yup": "^0.32.11"
},
"devDependencies": {
"@types/node": "^12.0.0",
......
import { Button, Menu, Layout } from 'antd';
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
import {
BrowserRouter,
Link,
Route,
Switch,
useHistory
} from 'react-router-dom';
import {
HomeOutlined,
RocketOutlined,
DollarOutlined,
LogoutOutlined,
LoginOutlined,
BookOutlined
BookOutlined,
FundOutlined
} from '@ant-design/icons';
import './App.css';
const { Content } = Layout;
import { useMemo } from 'react';
import Encyclopedia from './pages/Encyclopedia';
import Game from './pages/Game';
import Home from './pages/Home';
import Leaderboard from './pages/Leaderboard';
import Login from './pages/Login';
import Home from './pages/Home/Home';
import Leaderboard from './pages/Leaderboard/Leaderboard';
import Store from './pages/Store';
import { signOut } from './utils/firebase';
import useLoggedInUser from './hooks/useLoggedInUser';
const TopMenu = () => (
<Menu style={{ padding: '0 px' }} mode="horizontal">
<Menu.Item key="home" icon={<HomeOutlined />}>
<Link to="/">
<Button type="text">Home</Button>
</Link>
</Menu.Item>
<Menu.Item key="game" icon={<RocketOutlined />}>
<Link to="/game">
<Button type="text">Game</Button>
</Link>
</Menu.Item>
<Menu.Item key="store" icon={<DollarOutlined />}>
<Link to="/store">
<Button type="text">Store</Button>
</Link>
</Menu.Item>
<Menu.Item key="encyclopedia" icon={<BookOutlined />}>
<Link to="/encyclopedia">
<Button type="text">Encyclopedia</Button>
</Link>
</Menu.Item>
<Menu.Item key="login" icon={<LoginOutlined />}>
<Link to="/login">
<Button type="text">Login</Button>
</Link>
</Menu.Item>
<Menu.Item key="logout" icon={<LogoutOutlined />}>
<Button type="text">Logout</Button>
</Menu.Item>
</Menu>
);
const TopMenu = () => {
const currentUser = useLoggedInUser();
const isLoggedIn = useMemo(() => currentUser !== undefined, [currentUser]);
const { push } = useHistory();
const AppContent = () => (
<Content style={{ padding: '0 50px' }}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/login" exact component={Login} />
<Route path="/store" exact component={Store} />
<Route path="/encyclopedia" exact component={Encyclopedia} />
<Route path="/leaderboard" exact component={Leaderboard} />
<Route path="/game" exact component={Game} />
</Switch>
</Content>
);
return (
<Menu style={{ padding: '0 px' }} mode="horizontal">
<Menu.Item key="home" icon={<HomeOutlined />}>
<Link to="/">
<Button type="text">Home</Button>
</Link>
</Menu.Item>
{isLoggedIn && (
<>
<Menu.Item key="game" icon={<RocketOutlined />}>
<Link to="/game">
<Button type="text">Game</Button>
</Link>
</Menu.Item>
<Menu.Item key="store" icon={<DollarOutlined />}>
<Link to="/store">
<Button type="text">Store</Button>
</Link>
</Menu.Item>
<Menu.Item key="encyclopedia" icon={<BookOutlined />}>
<Link to="/encyclopedia">
<Button type="text">Encyclopedia</Button>
</Link>
</Menu.Item>
<Menu.Item key="leaderboard" icon={<FundOutlined />}>
<Link to="/leaderboard">
<Button type="text">Leaderboard</Button>
</Link>
</Menu.Item>
<Menu.Item key="logout" icon={<LogoutOutlined />}>
<Button
type="text"
onClick={async () => {
await signOut();
push('/');
}}
>
Logout
</Button>
</Menu.Item>
</>
)}
</Menu>
);
};
const AppContent = () => {
const currentUser = useLoggedInUser();
const isLoggedIn = useMemo(() => currentUser !== undefined, [currentUser]);
return (
<Content style={{ padding: '0 50px' }}>
<Switch>
<Route path="/" exact component={Home} />
{isLoggedIn && (
<>
<Route path="/store" exact component={Store} />
<Route path="/encyclopedia" exact component={Encyclopedia} />
<Route path="/leaderboard" exact component={Leaderboard} />
<Route path="/game" exact component={Game} />
</>
)}
</Switch>
</Content>
);
};
const App = () => (
<BrowserRouter>
......
import { UserData } from '../utils/firebase';
const dummyUserData: UserData[] = [
export const dummyUserData: UserData[] = [
{
userId: '1',
email: 'dummy1@example.com',
catchedPokemonIds: [1, 2, 3],
pokeballIds: [1],
foodIds: [1],
......@@ -12,6 +13,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '2',
email: 'dummy2@example.com',
catchedPokemonIds: [5, 8],
pokeballIds: [2],
foodIds: [],
......@@ -21,6 +23,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '3',
email: 'dummy3@example.com',
catchedPokemonIds: [6, 9],
pokeballIds: [],
foodIds: [1],
......@@ -30,6 +33,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '4',
email: 'dummy4@example.com',
catchedPokemonIds: [1, 2],
pokeballIds: [1],
foodIds: [1],
......@@ -39,6 +43,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '5',
email: 'dummy5@example.com',
catchedPokemonIds: [2],
pokeballIds: [1],
foodIds: [1],
......@@ -48,6 +53,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '6',
email: 'dummy6@example.com',
catchedPokemonIds: [3],
pokeballIds: [1],
foodIds: [1],
......@@ -57,6 +63,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '7',
email: 'dummy7@example.com',
catchedPokemonIds: [1, 2, 3, 8],
pokeballIds: [1],
foodIds: [1],
......@@ -66,6 +73,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '8',
email: 'dummy8@example.com',
catchedPokemonIds: [1, 3, 4],
pokeballIds: [1],
foodIds: [1],
......@@ -75,6 +83,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '9',
email: 'dummy9@example.com',
catchedPokemonIds: [30, 31],
pokeballIds: [1],
foodIds: [1],
......@@ -84,6 +93,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '10',
email: 'dummy10@example.com',
catchedPokemonIds: [100],
pokeballIds: [1],
foodIds: [1],
......@@ -93,6 +103,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '11',
email: 'dummy11@example.com',
catchedPokemonIds: [82, 83],
pokeballIds: [1],
foodIds: [1],
......@@ -102,6 +113,7 @@ const dummyUserData: UserData[] = [
},
{
userId: '12',
email: 'dummy12@example.com',
catchedPokemonIds: [84, 86],
pokeballIds: [1],
foodIds: [1],
......@@ -110,3 +122,6 @@ const dummyUserData: UserData[] = [
highestSecondsAlive: 19
}
];
export const getDummyUserData = (id: string): UserData =>
dummyUserData.filter(obj => obj.userId === id)[0];
import { User } from 'firebase/auth';
// necessary subset of firebase User
export const dummyUsers = [
{
uid: '1',
email: 'dummy1@example.com'
},
{
uid: '2',
email: 'dummy2@example.com'
},
{
uid: '3',
email: 'dummy3@example.com'
},
{
uid: '4',
email: 'dummy4@example.com'
},
{
uid: '5',
email: 'dummy5@example.com'
},
{
uid: '6',
email: 'dummy6@example.com'
},
{
uid: '7',
email: 'dummy7@example.com'
},
{
uid: '8',
email: 'dummy8@example.com'
},
{
uid: '9',
email: 'dummy9@example.com'
},
{
uid: '10',
email: 'dummy10@example.com'
},
{
uid: '11',
email: 'dummy11@example.com'
},
{
uid: '12',
email: 'dummy12@example.com'
}
] as User[];
......@@ -4,13 +4,14 @@ export type Pokeballs = {
id: number;
name: string;
price: number;
catches: number;
image?: string;
};
export const pokeballs: Pokeballs[] = [
{ id: 1, name: 'Pokeball 1', price: 12 },
{ id: 2, name: 'Pokeball 2', price: 48 },
{ id: 3, name: 'Pokeball 3', price: 19 }
{ id: 1, name: 'Pokeball 1', price: 12, catches: 1 },
{ id: 2, name: 'Pokeball 2', price: 48, catches: 2 },
{ id: 3, name: 'Pokeball 3', price: 19, catches: 3 }
];
export const getUserPokeballs = (pokeballIds: number[]): Pokeballs[] =>
......
import { onSnapshot } from '@firebase/firestore';
import { useEffect, useState } from 'react';
import { UserData, userDataCollection } from '../utils/firebase';
const useAllUserData = () => {
const [allUserData, setAllUserData] = useState<UserData[]>();
useEffect(() => {
const unsubscribe = onSnapshot(userDataCollection, snapshot => {
setAllUserData(snapshot.docs.map(doc => doc.data()));
});
return () => {
unsubscribe();
};
}, []);
return allUserData;
};
export default useAllUserData;
import { ChangeEvent, useCallback, useState } from 'react';
// TODO: rewrite to work with antd (maybe works, not tested)
const useField = (id: string, required?: boolean) => {
const [value, setValue] = useState('');
const [touched, setTouched] = useState(false);
......
......@@ -2,7 +2,7 @@ import { useEffect } from 'react';
const usePageTitle = (title: string) => {
useEffect(() => {
document.title = `${title} | Tic Tac Toe`;
document.title = `${title} | Pokemon Game`;
}, [title]);
};
......
import { onSnapshot } from '@firebase/firestore';
import { useEffect, useState } from 'react';
import { UserData, userDataCollection } from '../utils/firebase';
const useUserData = (uid: string) => {
const [userData, setUserData] = useState<UserData>();
useEffect(() => {
const unsubscribe = onSnapshot(userDataCollection, snapshot => {
const allUserData = snapshot.docs.map(doc => doc.data());
setUserData(allUserData.filter(ud => ud.userId === uid)[0]);
});
return () => {
unsubscribe();
};
}, []);
return userData;
};
export default useUserData;
import { Typography } from 'antd';
const Encyclopedia = () => <Typography>Encyclopedia</Typography>;
import usePageTitle from '../hooks/usePageTitle';
const Encyclopedia = () => {
usePageTitle('Encyclopedia');
return <Typography>Encyclopedia</Typography>;
};
export default Encyclopedia;
import { Typography } from 'antd';
const Game = () => <Typography>Game</Typography>;
import usePageTitle from '../hooks/usePageTitle';
const Game = () => {
usePageTitle('Game');
return <Typography>Game</Typography>;
};
export default Game;
import { Typography } from 'antd';
const Home = () => <Typography>Home</Typography>;
export default Home;
import {
EyeInvisibleOutlined,
EyeTwoTone,
LockOutlined,
UserOutlined
} from '@ant-design/icons';
import { Input, message, Modal } from 'antd';
import { Controller, useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { useCallback } from 'react';
import { ErrorMessage } from '@hookform/error-message';
import { signIn, signUp } from '../../utils/firebase';
type AuthModalProps = {
type?: AuthModalType;
title?: string;
isVisible: boolean;
setAuthModalType: (_: undefined | AuthModalType) => void;
setIsAuthModalVisible: (_: boolean) => void;
};
type FormInput = {
email: string;
password: string;
};
export enum AuthModalType {
SignIn,
SignUp
}
export const getAuthModalTitle = (type?: AuthModalType) => {
switch (type) {
case AuthModalType.SignIn:
return 'Sign In';
case AuthModalType.SignUp:
return 'Sign Up';
}
};
const schema = yup
.object({
email: yup
.string()
.required('Email is required')
.email('Email must be valid'),
password: yup
.string()
.required('Password is required')
.min(6, 'Password must be at least 6 characters')
})
.required();
const errorRenderer = ({ message }: { message: string }) => (
<p style={{ color: 'red' }}>{message}</p>
);
export const AuthModal = ({
type,
title,
isVisible,
setAuthModalType,
setIsAuthModalVisible
}: AuthModalProps) => {
const {
control,
formState: { errors },
handleSubmit
} = useForm<FormInput>({
resolver: yupResolver(schema)
});
const closeModal = useCallback(() => {
setAuthModalType(undefined);
setIsAuthModalVisible(false);
}, [setAuthModalType, setIsAuthModalVisible]);
const onOk = useCallback(
async ({ email, password }: FormInput) => {
console.log(type);
try {
switch (type) {
case AuthModalType.SignUp:
await signUp(email, password);
break;
case AuthModalType.SignIn:
await signIn(email, password);
break;
}
closeModal();
} catch (err) {
const submitError =
(err as { message?: string })?.message ?? 'Unknown error occurred';
message.error(submitError);
}
},
[setAuthModalType, setIsAuthModalVisible, type]
);
if (type === undefined || title === undefined) {
return null;
}
return (
<Modal
title={title}
visible={isVisible && type !== undefined}
okText={title}
onOk={handleSubmit(onOk)}
onCancel={closeModal}
>
<Controller
name="email"
control={control}
defaultValue=""
render={({ field }) => (
<Input
{...field}
size="large"
placeholder="Enter your email"
prefix={<UserOutlined />}
/>
)}
/>
<ErrorMessage errors={errors} name="email" render={errorRenderer} />
<div style={{ marginTop: 10 }}>
<Controller
name="password"
control={control}
defaultValue=""
render={({ field }) => (
<Input.Password
{...field}
size="large"
placeholder="Enter your password"
iconRender={visible =>
visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />
}
prefix={<LockOutlined />}
/>
)}
/>
</div>
<ErrorMessage errors={errors} name="password" render={errorRenderer} />
</Modal>
);
};
import { Button, Typography } from 'antd';
import { useMemo, useState } from 'react';
import useLoggedInUser from '../../hooks/useLoggedInUser';
import usePageTitle from '../../hooks/usePageTitle';
import { AuthModal, AuthModalType, getAuthModalTitle } from './AuthModal';
import { HomeCarousel } from './HomeCarousel';
const Home = () => {
usePageTitle('Home');
const currentUser = useLoggedInUser();
const isLoggedIn = useMemo(() => currentUser !== undefined, [currentUser]);