Skip to content

Commit

Permalink
Create error page for product not found
Browse files Browse the repository at this point in the history
  • Loading branch information
VaiTon committed Sep 5, 2023
1 parent 0d1837c commit 99e9b55
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 123 deletions.
8 changes: 7 additions & 1 deletion src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
interface Error {
errors?: {
field: { id: string; value: string };
impact: { lc_name: string; name: string; id: string };
message: { lc_name: string; name: string; id: string };
}[];
}
// interface Locals {}
// interface PageData {}
// interface Platform {}
Expand Down
45 changes: 37 additions & 8 deletions src/lib/api/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,47 @@ import type { Nutriments } from './nutriments';
import { preferences } from '$lib/settings';
import { wrapFetch } from '$lib/utils';

export type ProductState<T = Product> = {
status: 'success' | 'success_with_warnings' | 'success_with_errors' | 'failure';
export type ProductStateBase = {
result: {
id: string;
name: string;
lc_name: string;
};
errors: object[];
warnings: object[];
};

export type ProductStateFailure = ProductStateBase & {
status: 'failure';
errors: {
field: { id: string; value: string };
impact: { lc_name: string; name: string; id: string };
message: { lc_name: string; name: string; id: string };
}[];
};

export type ProductStateFound<T = Product> = ProductStateBase & {
product: T;
};

export type ProductStateSuccess<T = Product> = ProductStateFound<T> & {
status: 'success';
};

export type ProductStateSuccessWithWarnings<T = Product> = ProductStateFound<T> & {
status: 'success_with_warnings';
warnings: object[];
};

export type ProductStateSuccessWithErrors<T = Product> = ProductStateFound<T> & {
status: 'success_with_errors';
errors: object[];
};

export type ProductState<T = Product> =
| ProductStateSuccess<T>
| ProductStateSuccessWithWarnings<T>
| ProductStateSuccessWithErrors<T>
| ProductStateFailure;

export type ProductSearch<T = Product> = {
count: number;
page: number;
Expand Down Expand Up @@ -99,7 +127,7 @@ export async function getProduct(
export async function getProductReducedForCard(
barcode: string,
fetch: (url: string) => Promise<Response>
): Promise<ProductReduced> {
): Promise<ProductState<ProductReduced>> {
const url =
PRODUCT_URL(barcode) +
'?' +
Expand All @@ -110,13 +138,13 @@ export async function getProductReducedForCard(
const res = await wrapFetch(fetch)(url);
const productState = (await res.json()) as ProductState<ProductReduced>;

return productState.product;
return productState;
}

export async function getProductName(
fetch: (url: string) => Promise<Response>,
barcode: string
): Promise<Pick<Product, 'product_name'>> {
): Promise<Pick<Product, 'product_name'> | null> {
const url =
PRODUCT_URL(barcode) +
'?' +
Expand All @@ -128,7 +156,8 @@ export async function getProductName(
const res = await wrapFetch(fetch)(url);
const productState = (await res.json()) as ProductState<Pick<Product, 'product_name'>>;

return productState.product;
if (productState.status !== 'success') return null;
else return productState.product;
}

export async function addOrEditProductV2(
Expand Down
2 changes: 0 additions & 2 deletions src/lib/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,3 @@ export const PRODUCT_URL = (barcode: string) => `${API_HOST}/api/v3/product/${ba
export const SEARCH_URL = `${API_HOST}/api/v2/search`;

export const USER_AGENT = `Open Food Facts Explorer (${import.meta.env.PACKAGE_VERSION})`;

console.debug('User agent', USER_AGENT);
4 changes: 1 addition & 3 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
export const wrapFetch =
(fetch: (url: string, options?: RequestInit) => Promise<Response>) =>
(url: string, options?: RequestInit) =>
fetch(url, {
...options
});
fetch(url, { ...options });
38 changes: 38 additions & 0 deletions src/routes/+error.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import { page } from '$app/stores';
</script>

<div class="alert alert-error">
<div>
<h1 class="font-bold text-xl mb-3">
{$page.error?.message}
</h1>

{#if $page.error?.errors != null}
{@const errors = $page.error.errors}

<p>
{errors.length} error{#if errors.length > 1}s{/if} occurred:
</p>

<ul class="list-disc">
{#each errors as error}
<li class="ms-4 mb-2">
<h3>
<span class="font-bold me-2">
{error['impact']['lc_name']}:
</span>
{error['message']['lc_name']}
(id: <code class="font-mono">{error['message']['id']}</code>)
</h3>
<p class="text-xs font-mono">
{#if error.field}
Caused by field `{error.field.id}` with value `{error.field.value}`
{/if}
</p>
</li>
{/each}
</ul>
{/if}
</div>
</div>
188 changes: 95 additions & 93 deletions src/routes/products/[barcode]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,108 +15,110 @@
$: lang = $preferences.lang;
</script>

{#if data.state.status === 'success'}
<Card>
<div class="md:flex max-md:text-center gap-2 max-md:mb-4">
<h1 class="font-bold text-3xl md:text-4xl my-4 grow">
{product.product_name ?? product.code}
</h1>
<Card>
<div class="md:flex max-md:text-center gap-2 max-md:mb-4">
<h1 class="font-bold text-3xl md:text-4xl my-4 grow">
{product.product_name ?? product.code}
</h1>

<a
href={'https://world.openfoodfacts.org/product/' + product.code}
target="_blank"
rel="noopener noreferrer"
class="btn max-sm:btn-sm btn-secondary ml-auto"
>
See on OpenFoodFacts
</a>
<a href={`/products/${product.code}/edit`} class="btn max-sm:btn-sm btn-secondary ml-auto">
Edit
</a>
</div>
<a
href={'https://world.openfoodfacts.org/product/' + product.code}
target="_blank"
rel="noopener noreferrer"
class="btn max-sm:btn-sm btn-secondary ml-auto"
>
See on OpenFoodFacts
</a>
<a href={`/products/${product.code}/edit`} class="btn max-sm:btn-sm btn-secondary ml-auto">
Edit
</a>
</div>

<div class="flex gap-4">
<div class="flex-grow">
<div class="grid grid-cols-[max-content,1fr] gap-x-4 gap-y-1">
<span class="font-bold text-end">Quantity:</span>
<span>{product.quantity}</span>
<div class="flex gap-4">
<div class="flex-grow">
<div class="grid grid-cols-[max-content,1fr] gap-x-4 gap-y-1">
<span class="font-bold text-end">Quantity:</span>
<span>{product.quantity}</span>

<span class="font-bold text-end">Brands:</span>
<span>
{#await data.taxo.brands}
Loading...
{:then brands}
{#each product.brands_tags as tag, i}
{#if i > 0}, {/if}
{brands[tag] != null ? getOrDefault(brands[tag].name, lang) : tag}
{/each}
{/await}
</span>
<span class="font-bold text-end">Brands:</span>
<span>
{#await data.taxo.brands}
Loading...
{:then brands}
{#each product.brands_tags as tag, i}
{#if i > 0},
{/if}
{brands[tag] != null ? getOrDefault(brands[tag].name, lang) : tag}
{/each}
{/await}
</span>

<span class="font-bold text-end">Categories:</span>
<span>
{#await data.taxo.categories}
Loading...
{:then categories}
{#each product.categories_tags as tag, i}
{#if i > 0}, {/if}
<a class="link" href={'/taxo/categories/' + tag}
>{categories[tag] != null ? getOrDefault(categories[tag].name, lang) : tag}</a
>
{/each}
{/await}
</span>
<span class="font-bold text-end">Categories:</span>
<span>
{#await data.taxo.categories}
Loading...
{:then categories}
{#each product.categories_tags as tag, i}
{#if i > 0},
{/if}
<a class="link" href={'/taxo/categories/' + tag}
>{categories[tag] != null ? getOrDefault(categories[tag].name, lang) : tag}</a
>
{/each}
{/await}
</span>

<span class="font-bold text-end">Stores:</span>
<span>
{#await data.taxo.stores}
Loading...
{:then stores}
{#each product.stores_tags as tag, i}
{#if i > 0}, {/if}
{stores[tag] != null ? getOrDefault(stores[tag].name, lang) : tag}
{/each}
{/await}
</span>
<span class="font-bold text-end">Stores:</span>
<span>
{#await data.taxo.stores}
Loading...
{:then stores}
{#each product.stores_tags as tag, i}
{#if i > 0},
{/if}
{stores[tag] != null ? getOrDefault(stores[tag].name, lang) : tag}
{/each}
{/await}
</span>

<span class="font-bold text-end">Labels:</span>
<span>
{#await data.taxo.labels}
Loading...
{:then labels}
{#each product.labels_tags as tag, i}
{#if i > 0}, {/if}
<a class="link" href={'/taxo/labels/' + tag}
>{labels[tag] != null ? getOrDefault(labels[tag].name, lang) : tag}</a
>
{/each}
{/await}
</span>
</div>
</div>
<div>
<img
src={product.image_front_url}
alt={product.product_name}
class="w-32 float-right rounded-lg"
/>
<span class="font-bold text-end">Labels:</span>
<span>
{#await data.taxo.labels}
Loading...
{:then labels}
{#each product.labels_tags as tag, i}
{#if i > 0},
{/if}
<a class="link" href={'/taxo/labels/' + tag}
>{labels[tag] != null ? getOrDefault(labels[tag].name, lang) : tag}</a
>
{/each}
{/await}
</span>
</div>
</div>
</Card>

<div class="p-3 w-full flex gap-4 justify-evenly">
<NutriScore grade={product.nutriscore_grade} />
<Nova grade={product.nova_group} />
<EcoScore grade={product.ecoscore_grade} />
<div>
<img
src={product.image_front_url}
alt={product.product_name}
class="w-32 float-right rounded-lg"
/>
</div>
</div>
</Card>

<KnowledgePanels knowledgePanels={product.knowledge_panels} />
<div class="p-3 w-full flex gap-4 justify-evenly">
<NutriScore grade={product.nutriscore_grade} />
<Nova grade={product.nova_group} />
<EcoScore grade={product.ecoscore_grade} />
</div>

<Card>
<h1 class="text-4xl my-4 font-bold">
Folksonomy Engine <span class="font-light italic">(alpha)</span>
</h1>
<KnowledgePanels knowledgePanels={product.knowledge_panels} />

<Card>
<h1 class="text-4xl my-4 font-bold">
Folksonomy Engine <span class="font-light italic">(alpha)</span>
</h1>

<Folksonomy tags={data.tags ?? []} keys={data.keys.map((it) => it.k)} barcode={product.code} />
</Card>
{/if}
<Folksonomy tags={data.tags ?? []} keys={data.keys.map((it) => it.k)} barcode={product.code} />
</Card>
30 changes: 22 additions & 8 deletions src/routes/products/[barcode]/+page.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import type { PageLoad } from './$types';
import { getKeys, getProduct, getProductFolksonomy, getTaxo } from '$lib/api';
import {
type Brand,
type Label,
getKeys,
getProduct,
getProductFolksonomy,
getTaxo,
type Store,
type Category
} from '$lib/api';
import { error } from '@sveltejs/kit';

export const load = (async ({ params, fetch }) => {
const state = getProduct(params.barcode, fetch);
const categories = getTaxo('categories', fetch);
const labels = getTaxo('labels', fetch);
const stores = getTaxo('stores', fetch);
const brands = getTaxo('brands', fetch);
export const load: PageLoad = async ({ params, fetch }) => {
const state = await getProduct(params.barcode, fetch);
if (state.status === 'failure') {
throw error(404, { message: 'Failure to load product', errors: state.errors });
}

const categories = getTaxo<Category>('categories', fetch);
const labels = getTaxo<Label>('labels', fetch);
const stores = getTaxo<Store>('stores', fetch);
const brands = getTaxo<Brand>('brands', fetch);

const tags = getProductFolksonomy(params.barcode, fetch);
const keys = getKeys(fetch);
Expand All @@ -22,4 +36,4 @@ export const load = (async ({ params, fetch }) => {
brands
}
};
}) satisfies PageLoad;
};
Loading

1 comment on commit 99e9b55

@vercel
Copy link

@vercel vercel bot commented on 99e9b55 Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.