add functionality to dashboard oops
This commit is contained in:
parent
36acc99729
commit
b9737def53
|
@ -304,3 +304,15 @@ SECTIONS.forEach(section => {
|
|||
});
|
||||
|
||||
export { moduleIDToSectionMap };
|
||||
|
||||
let moduleIDToURLMap: {[key: string]: string} = {};
|
||||
|
||||
SECTIONS.forEach(section => {
|
||||
MODULE_ORDERING[section].forEach(category => {
|
||||
category.items.forEach(moduleID => {
|
||||
moduleIDToURLMap[moduleID] = `/${section}/${moduleID}`;
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
export { moduleIDToURLMap };
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import * as React from 'react';
|
||||
import { Link } from 'gatsby';
|
||||
|
||||
type ActiveItemStatus = 'Solving' | 'Skipped' | 'In Progress';
|
||||
|
||||
export type ActiveItem = {
|
||||
label: string;
|
||||
status: ActiveItemStatus;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const statusClasses: { [key in ActiveItemStatus]: string } = {
|
||||
Solving: 'bg-yellow-100 text-yellow-800',
|
||||
Skipped: 'bg-gray-100 text-gray-800',
|
||||
'In Progress': 'bg-green-100 text-green-800',
|
||||
};
|
||||
|
||||
export default function ActiveItems({
|
||||
type,
|
||||
items,
|
||||
}: {
|
||||
type: 'problems' | 'modules';
|
||||
items: ActiveItem[];
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-white shadow rounded-lg mb-8">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Active {type === 'problems' ? 'Problems' : 'Modules'}
|
||||
</h3>
|
||||
{/*<div className="mt-3 max-w-xl text-gray-500">*/}
|
||||
{/* <p className="mb-2">*/}
|
||||
{/* <a*/}
|
||||
{/* href="#"*/}
|
||||
{/* className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"*/}
|
||||
{/* >*/}
|
||||
{/* Intro: Input & Output*/}
|
||||
{/* <span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-green-100 text-green-800">*/}
|
||||
{/* Practicing*/}
|
||||
{/* </span>*/}
|
||||
{/* </a>*/}
|
||||
{/* </p>*/}
|
||||
{/* <p>*/}
|
||||
{/* <a*/}
|
||||
{/* href="#"*/}
|
||||
{/* className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"*/}
|
||||
{/* >*/}
|
||||
{/* Intro: Expected Knowledge*/}
|
||||
{/* <span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-gray-100 text-gray-800">*/}
|
||||
{/* Skipped*/}
|
||||
{/* </span>*/}
|
||||
{/* </a>*/}
|
||||
{/* </p>*/}
|
||||
{/*</div>*/}
|
||||
<div className="mt-4 text-gray-500">
|
||||
{items.map((item, idx) => (
|
||||
<p className={idx === 0 ? '' : 'mt-2'}>
|
||||
<Link
|
||||
to={item.url}
|
||||
className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"
|
||||
>
|
||||
{item.label}
|
||||
<span
|
||||
className={
|
||||
'ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 ' +
|
||||
statusClasses[item.status]
|
||||
}
|
||||
>
|
||||
{item.status}
|
||||
</span>
|
||||
</Link>
|
||||
</p>
|
||||
))}
|
||||
{/*<p>*/}
|
||||
{/* <a*/}
|
||||
{/* href="#"*/}
|
||||
{/* className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"*/}
|
||||
{/* >*/}
|
||||
{/* Longest Common Subsequence*/}
|
||||
{/* <span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-gray-100 text-gray-800">*/}
|
||||
{/* Skipped*/}
|
||||
{/* </span>*/}
|
||||
{/* </a>*/}
|
||||
{/*</p>*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
import * as React from 'react';
|
||||
// @ts-ignore
|
||||
import logo from '../../assets/logo.svg';
|
||||
// @ts-ignore
|
||||
import logoSquare from '../../assets/logo-square.png';
|
||||
|
||||
export default function DashboardNav() {
|
||||
return (
|
||||
<nav className="bg-white shadow">
|
||||
<div className="max-w-7xl mx-auto px-2 sm:px-4 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex px-2 lg:px-0">
|
||||
<div className="flex-shrink-0 flex items-center">
|
||||
<img
|
||||
className="block sm:hidden h-8 w-auto"
|
||||
src={logoSquare}
|
||||
alt="USACO Guide"
|
||||
/>
|
||||
<img
|
||||
className="hidden sm:block h-12 w-auto"
|
||||
src={logo}
|
||||
alt="USACO Guide"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden lg:ml-6 xl:ml-12 lg:flex">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center px-1 pt-1 border-b-2 border-indigo-500 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Intro
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Bronze
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Silver
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Gold
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Plat
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Advanced
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center px-2 md:px-0 lg:ml-6 lg:justify-end">
|
||||
<div className="max-w-lg w-full lg:max-w-xs">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
Search
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="search"
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:border-blue-300 focus:shadow-outline-blue sm:text-sm transition duration-150 ease-in-out"
|
||||
placeholder="Search"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center lg:hidden">
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
|
||||
aria-label="Main menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{/* Icon when menu is closed. */}
|
||||
{/* Menu open: "hidden", Menu closed: "block" */}
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
{/* Icon when menu is open. */}
|
||||
{/* Menu open: "block", Menu closed: "hidden" */}
|
||||
<svg
|
||||
className="hidden h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
Mobile menu, toggle classes based on menu state.
|
||||
|
||||
Menu open: "block", Menu closed: "hidden"
|
||||
*/}
|
||||
<div className="hidden lg:hidden">
|
||||
<div className="pt-2 pb-3">
|
||||
<a
|
||||
href="#"
|
||||
className="block pl-3 pr-4 py-2 border-l-4 border-indigo-500 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Team
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Projects
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Calendar
|
||||
</a>
|
||||
</div>
|
||||
<div className="pt-4 pb-3 border-t border-gray-200">
|
||||
<div className="flex items-center px-4">
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
className="h-10 w-10 rounded-full"
|
||||
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<div className="text-base font-medium leading-6 text-gray-800">
|
||||
Tom Cook
|
||||
</div>
|
||||
<div className="text-sm font-medium leading-5 text-gray-500">
|
||||
tom@example.com
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<a
|
||||
href="#"
|
||||
className="block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100 focus:outline-none focus:text-gray-800 focus:bg-gray-100 transition duration-150 ease-in-out"
|
||||
>
|
||||
Your Profile
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100 focus:outline-none focus:text-gray-800 focus:bg-gray-100 transition duration-150 ease-in-out"
|
||||
>
|
||||
Settings
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100 focus:outline-none focus:text-gray-800 focus:bg-gray-100 transition duration-150 ease-in-out"
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { Link } from 'gatsby';
|
||||
import * as React from 'react';
|
||||
|
||||
export default function WelcomeBackBanner({
|
||||
lastViewedModuleURL,
|
||||
lastViewedModuleLabel,
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-blue-700 shadow hover:shadow-lg transition duration-150 ease-in-out sm:rounded-lg w-full">
|
||||
<Link
|
||||
className="px-4 py-5 sm:p-6 lg:p-8 block sm:flex sm:items-center sm:justify-between"
|
||||
to={lastViewedModuleURL || '/intro/using-this-guide'}
|
||||
>
|
||||
<div>
|
||||
<h3 className="text-2xl leading-7 font-medium text-white">
|
||||
{lastViewedModuleURL
|
||||
? 'Welcome Back!'
|
||||
: 'Welcome to the USACO Guide!'}
|
||||
</h3>
|
||||
<div className="mt-2 leading-5 text-teal-200">
|
||||
<p>
|
||||
{lastViewedModuleURL
|
||||
? `Pick up where you left off. Your last viewed module was ${lastViewedModuleLabel}.`
|
||||
: `Get started on the first module, "Using This Guide."`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center mr-2">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center px-8 py-3 border border-transparent text-lg font-medium rounded-md text-white bg-blue-800 hover:bg-blue-600 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150"
|
||||
>
|
||||
{lastViewedModuleURL
|
||||
? `Continue: ${lastViewedModuleLabel}`
|
||||
: `Get Started: Using This Guide!`}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -24,6 +24,9 @@ const UserDataContext = createContext<{
|
|||
problem: Problem,
|
||||
status: ProblemProgress
|
||||
) => void;
|
||||
|
||||
lastViewedModule: string;
|
||||
setLastViewedModule: (moduleID: string) => void;
|
||||
}>({
|
||||
lang: 'showAll',
|
||||
setLang: null,
|
||||
|
@ -31,6 +34,8 @@ const UserDataContext = createContext<{
|
|||
setModuleProgress: null,
|
||||
userProgressOnProblems: null,
|
||||
setUserProgressOnProblems: null,
|
||||
lastViewedModule: null,
|
||||
setLastViewedModule: null,
|
||||
});
|
||||
|
||||
const langKey = 'guide:userData:lang';
|
||||
|
@ -70,6 +75,18 @@ const getProblemStatusFromStorage = () => {
|
|||
return v || {};
|
||||
};
|
||||
|
||||
const lastViewedModuleKey = 'guide:userData:lastViewedModule';
|
||||
const getLastViewedModuleFromStorage = () => {
|
||||
let stickyValue = window.localStorage.getItem(lastViewedModuleKey);
|
||||
let v = null;
|
||||
try {
|
||||
v = JSON.parse(stickyValue);
|
||||
} catch (e) {
|
||||
console.error("Couldn't parse last viewed module", e);
|
||||
}
|
||||
return v || null;
|
||||
};
|
||||
|
||||
export const UserDataProvider = ({ children }) => {
|
||||
const [lang, setLang] = useState<UserLang>('showAll');
|
||||
const [userProgress, setUserProgress] = useState<{
|
||||
|
@ -78,44 +95,57 @@ export const UserDataProvider = ({ children }) => {
|
|||
const [problemStatus, setProblemStatus] = useState<{
|
||||
[key: string]: ProblemProgress;
|
||||
}>({});
|
||||
const [lastViewedModule, setLastViewedModule] = useState<string>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
setLang(getLangFromStorage());
|
||||
setUserProgress(getProgressFromStorage());
|
||||
setProblemStatus(getProblemStatusFromStorage());
|
||||
setLastViewedModule(getLastViewedModuleFromStorage());
|
||||
}, []);
|
||||
|
||||
const userData = React.useMemo(
|
||||
() => ({
|
||||
lang: lang as UserLang,
|
||||
setLang: lang => {
|
||||
window.localStorage.setItem(langKey, JSON.stringify(lang));
|
||||
setLang(lang);
|
||||
},
|
||||
userProgressOnModules: userProgress,
|
||||
setModuleProgress: (moduleID: string, progress: ModuleProgress) => {
|
||||
const newProgress = {
|
||||
...getProgressFromStorage(),
|
||||
[moduleID]: progress,
|
||||
};
|
||||
window.localStorage.setItem(progressKey, JSON.stringify(newProgress));
|
||||
setUserProgress(newProgress);
|
||||
},
|
||||
userProgressOnProblems: problemStatus,
|
||||
setUserProgressOnProblems: (problem, status) => {
|
||||
const newStatus = {
|
||||
...getProblemStatusFromStorage(),
|
||||
[problem.uniqueID]: status,
|
||||
};
|
||||
window.localStorage.setItem(
|
||||
problemStatusKey,
|
||||
JSON.stringify(newStatus)
|
||||
);
|
||||
setProblemStatus(newStatus);
|
||||
},
|
||||
lastViewedModule,
|
||||
setLastViewedModule: moduleID => {
|
||||
window.localStorage.setItem(
|
||||
lastViewedModuleKey,
|
||||
JSON.stringify(moduleID)
|
||||
);
|
||||
setLastViewedModule(moduleID);
|
||||
},
|
||||
}),
|
||||
[lang, userProgress, problemStatus, lastViewedModule]
|
||||
);
|
||||
|
||||
return (
|
||||
<UserDataContext.Provider
|
||||
value={{
|
||||
lang: lang as UserLang,
|
||||
setLang: lang => {
|
||||
window.localStorage.setItem(langKey, JSON.stringify(lang));
|
||||
setLang(lang);
|
||||
},
|
||||
userProgressOnModules: userProgress,
|
||||
setModuleProgress: (moduleID: string, progress: ModuleProgress) => {
|
||||
const newProgress = {
|
||||
...getProgressFromStorage(),
|
||||
[moduleID]: progress,
|
||||
};
|
||||
window.localStorage.setItem(progressKey, JSON.stringify(newProgress));
|
||||
setUserProgress(newProgress);
|
||||
},
|
||||
userProgressOnProblems: problemStatus,
|
||||
setUserProgressOnProblems: (problem, status) => {
|
||||
const newStatus = {
|
||||
...getProblemStatusFromStorage(),
|
||||
[problem.uniqueID]: status,
|
||||
};
|
||||
window.localStorage.setItem(
|
||||
problemStatusKey,
|
||||
JSON.stringify(newStatus)
|
||||
);
|
||||
setProblemStatus(newStatus);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<UserDataContext.Provider value={userData}>
|
||||
{children}
|
||||
</UserDataContext.Provider>
|
||||
);
|
||||
|
|
|
@ -1,347 +1,78 @@
|
|||
import * as React from 'react';
|
||||
import { Link, PageProps } from 'gatsby';
|
||||
import { graphql, Link, PageProps } from 'gatsby';
|
||||
import Layout from '../components/layout';
|
||||
import SEO from '../components/seo';
|
||||
import { useState } from 'react';
|
||||
// @ts-ignore
|
||||
import logo from '../assets/logo.svg';
|
||||
// @ts-ignore
|
||||
import logoSquare from '../assets/logo-square.png';
|
||||
import SectionProgress from '../components/Dashboard/SectionProgress';
|
||||
import SectionProgressBar from '../components/Dashboard/SectionProgressBar';
|
||||
import UserDataContext from '../context/UserDataContext';
|
||||
import WelcomeBackBanner from '../components/Dashboard/WelcomeBackBanner';
|
||||
import {
|
||||
moduleIDToSectionMap,
|
||||
moduleIDToURLMap,
|
||||
SECTION_LABELS,
|
||||
} from '../../content/ordering';
|
||||
import DashboardNav from '../components/Dashboard/DashboardNav';
|
||||
import ActiveItems, { ActiveItem } from '../components/Dashboard/ActiveItems';
|
||||
|
||||
export default function DashboardPage(props: PageProps) {
|
||||
const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
|
||||
const { modules } = props.data as any;
|
||||
const moduleIDToName = modules.edges.reduce((acc, cur) => {
|
||||
acc[cur.node.frontmatter.id] = cur.node.frontmatter.title;
|
||||
return acc;
|
||||
}, {});
|
||||
const {
|
||||
lastViewedModule: lastViewedModuleID,
|
||||
userProgressOnModules,
|
||||
} = React.useContext(UserDataContext);
|
||||
const lastViewedModuleURL = moduleIDToURLMap[lastViewedModuleID];
|
||||
const activeModules: ActiveItem[] = React.useMemo(() => {
|
||||
return Object.keys(userProgressOnModules)
|
||||
.filter(
|
||||
x =>
|
||||
userProgressOnModules[x] === 'Reading' ||
|
||||
userProgressOnModules[x] === 'Practicing' ||
|
||||
userProgressOnModules[x] === 'Skipped'
|
||||
)
|
||||
.map(x => ({
|
||||
label: `${SECTION_LABELS[moduleIDToSectionMap[x]]}: ${
|
||||
moduleIDToName[x]
|
||||
}`,
|
||||
url: moduleIDToURLMap[x],
|
||||
status:
|
||||
userProgressOnModules[x] === 'Skipped' ? 'Skipped' : 'In Progress',
|
||||
}));
|
||||
}, [userProgressOnModules]);
|
||||
const activeProblems: ActiveItem[] = [];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<SEO title="Syllabus" />
|
||||
<SEO title="Dashboard" />
|
||||
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<nav className="bg-white shadow">
|
||||
<div className="max-w-7xl mx-auto px-2 sm:px-4 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex px-2 lg:px-0">
|
||||
<div className="flex-shrink-0 flex items-center">
|
||||
<img
|
||||
className="block sm:hidden h-8 w-auto"
|
||||
src={logoSquare}
|
||||
alt="USACO Guide"
|
||||
/>
|
||||
<img
|
||||
className="hidden sm:block h-12 w-auto"
|
||||
src={logo}
|
||||
alt="USACO Guide"
|
||||
/>
|
||||
</div>
|
||||
<div className="hidden lg:ml-6 xl:ml-12 lg:flex">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center px-1 pt-1 border-b-2 border-indigo-500 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Intro
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Bronze
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Silver
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Gold
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Plat
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Advanced
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center px-2 md:px-0 lg:ml-6 lg:justify-end">
|
||||
<div className="max-w-lg w-full lg:max-w-xs">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
Search
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="search"
|
||||
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:border-blue-300 focus:shadow-outline-blue sm:text-sm transition duration-150 ease-in-out"
|
||||
placeholder="Search"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center lg:hidden">
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
|
||||
aria-label="Main menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{/* Icon when menu is closed. */}
|
||||
{/* Menu open: "hidden", Menu closed: "block" */}
|
||||
<svg
|
||||
className="block h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M4 6h16M4 12h16M4 18h16"
|
||||
/>
|
||||
</svg>
|
||||
{/* Icon when menu is open. */}
|
||||
{/* Menu open: "block", Menu closed: "hidden" */}
|
||||
<svg
|
||||
className="hidden h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
Mobile menu, toggle classes based on menu state.
|
||||
|
||||
Menu open: "block", Menu closed: "hidden"
|
||||
*/}
|
||||
<div className="hidden lg:hidden">
|
||||
<div className="pt-2 pb-3">
|
||||
<a
|
||||
href="#"
|
||||
className="block pl-3 pr-4 py-2 border-l-4 border-indigo-500 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out"
|
||||
>
|
||||
Dashboard
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Team
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Projects
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out"
|
||||
>
|
||||
Calendar
|
||||
</a>
|
||||
</div>
|
||||
<div className="pt-4 pb-3 border-t border-gray-200">
|
||||
<div className="flex items-center px-4">
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
className="h-10 w-10 rounded-full"
|
||||
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<div className="text-base font-medium leading-6 text-gray-800">
|
||||
Tom Cook
|
||||
</div>
|
||||
<div className="text-sm font-medium leading-5 text-gray-500">
|
||||
tom@example.com
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<a
|
||||
href="#"
|
||||
className="block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100 focus:outline-none focus:text-gray-800 focus:bg-gray-100 transition duration-150 ease-in-out"
|
||||
>
|
||||
Your Profile
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100 focus:outline-none focus:text-gray-800 focus:bg-gray-100 transition duration-150 ease-in-out"
|
||||
>
|
||||
Settings
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="mt-1 block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100 focus:outline-none focus:text-gray-800 focus:bg-gray-100 transition duration-150 ease-in-out"
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<DashboardNav />
|
||||
|
||||
<main className="pt-4 pb-12">
|
||||
<div className="max-w-7xl mx-auto mb-4">
|
||||
<div className="flex overflow-x-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
{/*<div className="bg-white shadow sm:rounded-lg w-full max-w-sm">*/}
|
||||
{/* <div className="px-4 py-5 sm:p-6">*/}
|
||||
{/* <h3 className="text-lg leading-6 font-medium text-gray-900">*/}
|
||||
{/* Welcome to the USACO Guide!*/}
|
||||
{/* </h3>*/}
|
||||
{/* <div className="mt-2 max-w-xl text-sm leading-5 text-gray-500">*/}
|
||||
{/* <p>*/}
|
||||
{/* Get started on the first module, "Using this Guide."*/}
|
||||
{/* </p>*/}
|
||||
{/* </div>*/}
|
||||
{/* <div className="mt-3 text-sm leading-5">*/}
|
||||
{/* <a*/}
|
||||
{/* href="#"*/}
|
||||
{/* className="font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"*/}
|
||||
{/* >*/}
|
||||
{/* Get Started! →*/}
|
||||
{/* </a>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/*</div>*/}
|
||||
<div className="bg-blue-700 shadow hover:shadow-lg transition duration-150 ease-in-out sm:rounded-lg w-full">
|
||||
<Link
|
||||
className="px-4 py-5 sm:p-6 lg:p-8 block sm:flex sm:items-center sm:justify-between"
|
||||
to="/intro/using-guide"
|
||||
>
|
||||
<div>
|
||||
<h3 className="text-2xl leading-7 font-medium text-white">
|
||||
Welcome Back!
|
||||
</h3>
|
||||
<div className="mt-2 leading-5 text-teal-200">
|
||||
<p>
|
||||
Pick up where you left off. Your last viewed module was
|
||||
Input & Output.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center mr-2">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center px-8 py-3 border border-transparent text-lg font-medium rounded-md text-white bg-blue-800 hover:bg-blue-600 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150"
|
||||
>
|
||||
Continue: Input & Output
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<WelcomeBackBanner
|
||||
lastViewedModuleURL={lastViewedModuleURL}
|
||||
lastViewedModuleLabel={moduleIDToName[lastViewedModuleID]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 grid grid-cols-2 gap-8">
|
||||
<div>
|
||||
<div className="bg-white shadow sm:rounded-lg w-full mb-8">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Active Problems
|
||||
</h3>
|
||||
<div className="mt-4 max-w-xl text-gray-500">
|
||||
<p className="mb-2">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"
|
||||
>
|
||||
Promotion Counting
|
||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-yellow-100 text-yellow-800">
|
||||
Solving
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"
|
||||
>
|
||||
Longest Common Subsequence
|
||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-gray-100 text-gray-800">
|
||||
Skipped
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{activeProblems.length > 0 && (
|
||||
<div>
|
||||
<ActiveItems type="problems" items={activeProblems} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-white shadow sm:rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Active Modules
|
||||
</h3>
|
||||
<div className="mt-3 max-w-xl text-gray-500">
|
||||
<p className="mb-2">
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"
|
||||
>
|
||||
Intro: Input & Output
|
||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-green-100 text-green-800">
|
||||
Practicing
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="#"
|
||||
className="inline-flex items-center font-medium text-blue-600 hover:text-blue-500 transition ease-in-out duration-150"
|
||||
>
|
||||
Intro: Expected Knowledge
|
||||
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-gray-100 text-gray-800">
|
||||
Skipped
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeModules.length > 0 && (
|
||||
<div>
|
||||
<ActiveItems type="modules" items={activeModules} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<header>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
@ -460,3 +191,18 @@ export default function DashboardPage(props: PageProps) {
|
|||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query {
|
||||
modules: allMdx {
|
||||
edges {
|
||||
node {
|
||||
frontmatter {
|
||||
title
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -7,11 +7,17 @@ import { SECTION_LABELS } from '../../content/ordering';
|
|||
import { graphqlToModuleInfo } from '../utils';
|
||||
import SEO from '../components/seo';
|
||||
import ModuleLayout from '../components/ModuleLayout/ModuleLayout';
|
||||
import { useContext } from 'react';
|
||||
import UserDataContext from '../context/UserDataContext';
|
||||
|
||||
export default function Template(props) {
|
||||
const { mdx } = props.data; // data.markdownRemark holds your post data
|
||||
const { body } = mdx;
|
||||
const module = React.useMemo(() => graphqlToModuleInfo(mdx), [mdx]);
|
||||
const { setLastViewedModule } = useContext(UserDataContext);
|
||||
React.useEffect(() => {
|
||||
setLastViewedModule(module.id);
|
||||
}, []);
|
||||
return (
|
||||
<Layout>
|
||||
<SEO
|
||||
|
|
Reference in a new issue