Separate error views, minor refactors, stub interaction on practice page

This commit is contained in:
Thiago Chaves 2022-07-15 01:29:10 +03:00
parent 72249d9105
commit 6f600cf411
13 changed files with 184 additions and 113 deletions

View File

@ -19,5 +19,5 @@ export const Daily = Template.bind({});
Daily.args = {
name: "Daily",
description: "Words for daily practice",
word_count: 11,
expression_count: 11,
};

View File

@ -1,19 +1,19 @@
export interface ExpressionSetCardProps {
name: string;
description: string;
word_count: number;
expression_count: number;
}
export function ExpressionSetCard({
name,
description,
word_count,
expression_count,
}: ExpressionSetCardProps) {
return (
<article className="content-card">
<h2 className="content-row text-title margin-small">{name}</h2>
<span className="content-row text-meta margin-small">
{word_count} word(s)
{expression_count} expressions(s)
</span>
<p className="content-row text-details margin-paragraph">{description}</p>
</article>

View File

@ -5,14 +5,14 @@ export interface ExpressionSetInfo {
id: number;
name: string;
description: string;
word_count: number;
expression_count: number;
}
export function ExpressionSetInfo({
id,
name,
description,
word_count,
expression_count,
}: ExpressionSetInfo) {
return (
<section>
@ -38,7 +38,7 @@ export function ExpressionSetInfo({
</Link>
</h2>
<span className="content-row text-meta margin-small">
{word_count} word(s)
{expression_count} expression(s)
</span>
<p className="content-row text-details margin-paragraph">{description}</p>
</section>

View File

@ -0,0 +1,12 @@
import { Category, Expression, ExpressionId } from "../model";
import { useExpressionData } from "./useExpressionData";
export function useExpressionCategories(
expression_id?: ExpressionId | undefined
): Category[] {
const { categories, expression_to_category } = useExpressionData();
const category_ids = expression_to_category
.filter((item) => item.expression_id === expression_id)
.map((item) => item.category_id);
return categories.filter((item) => category_ids.includes(item.id));
}

View File

@ -0,0 +1,12 @@
import { Expression, ExpressionSetId } from "../model";
import { useExpressionData } from "./useExpressionData";
export function useExpressionsInSet(
expression_set_id: ExpressionSetId | undefined
): Expression[] {
const { expressions, expression_to_expression_set } = useExpressionData();
const expression_ids = expression_to_expression_set
.filter((item) => item.expression_set_id === expression_set_id)
.map((item) => item.expression_id);
return expressions.filter((item) => expression_ids.includes(item.id));
}

View File

@ -0,0 +1,14 @@
import { useRouter } from "next/router";
import { ExpressionSet } from "../model";
import { useExpressionData } from "./useExpressionData";
export function useQueriedExpressionSet(): ExpressionSet | undefined {
const { query } = useRouter();
const { expression_sets } = useExpressionData();
const expression_set_id = Number.parseInt(query["set-id"] as string);
if (!Number.isInteger(expression_set_id)) {
return undefined;
}
return expression_sets.find((item) => item.id === expression_set_id);
}

View File

@ -0,0 +1,11 @@
import { Expression, ExpressionSetId } from "../model";
import { sample } from "../util/array-utils";
import { useExpressionsInSet } from "./useExpressionsInSet";
export function useRandomExpressionInSet(
expression_set_id: ExpressionSetId | undefined
): Expression | undefined {
const expressions = useExpressionsInSet(expression_set_id);
if (expressions.length === 0) return undefined;
return sample(expressions);
}

View File

@ -1,33 +1,22 @@
import type { NextPage } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { ExpressionSetInfo } from "../../components/ExpressionSetInfo/ExpressionSetInfo";
import { Page } from "../../components/Page";
import { useExpressionData } from "../../hooks/useExpressionData";
import { useExpressionsInSet } from "../../hooks/useExpressionsInSet";
import { useQueriedExpressionSet } from "../../hooks/useQueriedExpressionSet";
import { PageWithError } from "../../views/PageWithError/PageWithError";
const ExpressionSetDetailsPage: NextPage = () => {
const { query } = useRouter();
const { expression_sets, expression_to_expression_set } = useExpressionData();
const expression_set = useQueriedExpressionSet();
const expressions = useExpressionsInSet(expression_set?.id);
// Fallback for expression set not found
const expression_set_id = Number.parseInt(query["set-id"] as string);
const expression_set = expression_sets.find(
(item) => item.id === expression_set_id
);
if (!expression_set)
return (
<Page>
<div className="page-with-padding scroll">
<p className="text-details">Expression set not found</p>
</div>
</Page>
);
if (!expression_set) {
return <PageWithError message="Expression set not found" />;
}
// Fallback for expression set empty TODO fix style
const word_count = expression_to_expression_set.filter(
(item) => item.expression_set_id === expression_set.id
).length;
if (!word_count)
// Fallback for expression set empty
if (expressions.length === 0)
return (
<Page>
<div className="page-with-padding scroll">
@ -35,7 +24,7 @@ const ExpressionSetDetailsPage: NextPage = () => {
id={expression_set.id}
name={expression_set.name}
description={expression_set.description}
word_count={word_count}
expression_count={expressions.length}
/>
<p className="text-details">No expressions left in this set.</p>
</div>
@ -50,7 +39,7 @@ const ExpressionSetDetailsPage: NextPage = () => {
id={expression_set.id}
name={expression_set.name}
description={expression_set.description}
word_count={word_count}
expression_count={expressions.length}
/>
</section>
<section className="navigation-bottom">

View File

@ -3,16 +3,32 @@ import Link from "next/link";
import { ExpressionSetCard } from "../../components/ExpressionSetCard";
import { Page } from "../../components/Page";
import { useExpressionData } from "../../hooks/useExpressionData";
import { useExpressionsInSet } from "../../hooks/useExpressionsInSet";
import { ExpressionSet } from "../../model";
const ExpressionSetListPage: NextPage = () => {
const { expression_sets, expression_to_expression_set } = useExpressionData();
const { expression_sets } = useExpressionData();
return (
<Page>
<div className="page-with-padding content-list scroll">
{expression_sets.map(({ id, name, description }) => (
<Link
<ExpressionSetLink
key={id}
id={id}
name={name}
description={description}
/>
))}
</div>
</Page>
);
};
function ExpressionSetLink({ id, description, name }: ExpressionSet) {
const expressions = useExpressionsInSet(id);
return (
<Link
href={{
pathname: "/expression-sets/details",
query: { "set-id": id },
@ -23,18 +39,11 @@ const ExpressionSetListPage: NextPage = () => {
<ExpressionSetCard
name={name}
description={description}
word_count={
expression_to_expression_set.filter(
(item) => item.expression_set_id === id
).length
}
expression_count={expressions.length}
/>
</a>
</Link>
))}
</div>
</Page>
);
};
}
export default ExpressionSetListPage;

View File

@ -1,86 +1,86 @@
import type { NextPage } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { ExpressionCard } from "../../components";
import { Page } from "../../components/Page";
import { useExpressionData } from "../../hooks/useExpressionData";
import { sample } from "../../util/array-utils";
import {
getCategoriesInExpression,
getExpressionsInSet,
} from "../../util/data-utils";
import { useExpressionCategories } from "../../hooks/useExpressionCategories";
import { useQueriedExpressionSet } from "../../hooks/useQueriedExpressionSet";
import { useRandomExpressionInSet } from "../../hooks/useRandomExpressionInSet";
import { Category, Expression } from "../../model";
import { PageWithError } from "../../views/PageWithError/PageWithError";
const ExpressionSetListPage: NextPage = () => {
const { query, pathname } = useRouter();
const {
expressions,
categories,
expression_sets,
expression_to_expression_set,
expression_to_category,
} = useExpressionData();
// Do random selection here so we don't keep flipping states with interaction
const ExpressionPracticePage: NextPage = () => {
const expression_set = useQueriedExpressionSet();
const expression = useRandomExpressionInSet(expression_set?.id);
const categories = useExpressionCategories(expression?.id);
// Fallback for expression set not found
const expression_set = expression_sets.find(
(item) => item.id === Number.parseInt(query["set-id"] as string)
);
if (!expression_set)
// Fallback views for expression set content not found
if (!expression_set) {
return <PageWithError message="Expression set not found" />;
}
if (!expression) {
return <PageWithError message="No expressions left in this set" />;
}
return (
<Page>
<p className="text-details">Expression set not found</p>
</Page>
<ExpressionCardPracticeView
key={expression.id}
expression={expression}
categories={categories}
/>
);
};
// Fallback for expression not found
const expression_candidates = getExpressionsInSet({
expression_set_id: expression_set.id,
expression_to_expression_set,
expressions,
});
if (expression_candidates.length === 0) {
return (
<Page>
<p className="text-details">No expressions left in this set</p>
</Page>
);
interface ExpressionCardPracticeViewProps {
expression: Expression;
categories: Category[];
}
// Choose word and find categories
// Find categories
const expression = sample(expression_candidates);
const expression_categories = getCategoriesInExpression({
// Handle internal state here
function ExpressionCardPracticeView({
expression,
categories,
expression_id: expression.id,
expression_to_category,
});
}: ExpressionCardPracticeViewProps) {
const { query, pathname } = useRouter();
const [revealed, setRevealed] = useState(false);
return (
<Page>
<div className="page-with-bottom-navigation">
<section className="padding-small scroll">
<ExpressionCard
prompt={expression.prompt}
categories={expression_categories.map((category) => category.name)}
categories={categories.map((category) => category.name)}
description={expression.description}
show_description
show_description={revealed}
/>
</section>
<section className="navigation-bottom">
{revealed ? (
<>
<Link href={{ pathname, query }} passHref>
<a className="navigation-item-bottom text-navigation grow">
<div>wrong</div>
<div>Wrong</div>
</a>
</Link>
<Link href={{ pathname, query }} passHref>
<a className="navigation-item-bottom text-navigation grow">
<div>right</div>
<div>Right</div>
</a>
</Link>
</>
) : (
<button
className="navigation-item-bottom text-navigation grow"
onClick={() => setRevealed(true)}
>
Reveal
</button>
)}
</section>
</div>
</Page>
);
};
}
export default ExpressionSetListPage;
export default ExpressionPracticePage;

View File

@ -93,6 +93,14 @@
height: 64px;
}
.navigation-item:hover {
background-color: antiquewhite;
}
.navigation-item.active {
background-color: bisque;
}
.navigation-item-bottom {
display: flex;
flex-shrink: 0;
@ -105,11 +113,11 @@
height: 80px;
}
.navigation-item:hover {
.navigation-item-bottom:hover {
background-color: antiquewhite;
}
.navigation-item.active {
.navigation-item-bottom.active {
background-color: bisque;
}

View File

@ -0,0 +1,15 @@
import { Page } from "../../components/Page";
export interface PageWithErrorProps {
message: string;
}
export function PageWithError({ message }: PageWithErrorProps) {
return (
<Page>
<div className="page-with-padding">
<p className="text-meta">{message}</p>
</div>
</Page>
);
}

View File

@ -0,0 +1 @@
export * from "./PageWithError";