Move State out of Helpers (#65)
This commit is contained in:
parent
5d43a1186d
commit
2101b2a416
|
@ -1,5 +1,5 @@
|
|||
import { avgRating } from '$helpers';
|
||||
import type { FullProduct, Tag } from '$types';
|
||||
import type { Product, Tag } from '$types';
|
||||
|
||||
export type TagArgs = {
|
||||
value: string;
|
||||
|
@ -10,33 +10,66 @@ export type RatingArgs = {
|
|||
value: string | number;
|
||||
};
|
||||
|
||||
/** Default value for filter selectors, saved in store */
|
||||
export const defaultFilter = { selectedCat: 0, selectedRating: 0 };
|
||||
|
||||
/** Rating greater than what is passed in */
|
||||
export const _ratingHigher = (product: FullProduct, { value }: RatingArgs) =>
|
||||
product.rating && avgRating(product.rating) >= Number(value);
|
||||
|
||||
/** Matches tags according to passed value */
|
||||
export const _matchesTag = (product: FullProduct, { value, $tags }: TagArgs): boolean => {
|
||||
try {
|
||||
if (product.tags) {
|
||||
const selectedTagId = $tags.find((tag) => tag.name === value)._id;
|
||||
const productTags = product.tags.map((tag) => tag._ref);
|
||||
if (productTags.includes(selectedTagId)) return true;
|
||||
} else return false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
export type FilterArgs = {
|
||||
type: string;
|
||||
value: string | number;
|
||||
products: Product[];
|
||||
tags?: Tag[];
|
||||
};
|
||||
|
||||
export type DefaultFilter = {
|
||||
selectedCat: string | number;
|
||||
selectedRating: string | number;
|
||||
};
|
||||
|
||||
/** Returns matching product tag from full list of tags */
|
||||
export const _findProductId = (tags: Tag[], name: string): string =>
|
||||
tags.find((tag) => tag.name === name)?._id;
|
||||
|
||||
/** Checks whether product has a tagId that matches passed id string */
|
||||
export const _productIncludesRef = (product: Product, id: string): boolean =>
|
||||
product.tags.map((tag) => tag._ref).includes(id) ? true : false;
|
||||
|
||||
/** Matches tags according to passed value */
|
||||
export const _matchesTag = (product: Product, { value, $tags }: TagArgs): boolean =>
|
||||
product?.tags ? _productIncludesRef(product, _findProductId($tags, value)) : true;
|
||||
|
||||
/** Rating greater than what is passed in */
|
||||
export const _ratingHigher = (product: Product, { value }: RatingArgs) =>
|
||||
product.rating && avgRating(product.rating) >= Number(value);
|
||||
|
||||
export const _getSingleArgs = (name, rating, tag, products, tags) =>
|
||||
name === 'rating'
|
||||
? { type: 'rating', value: rating, products }
|
||||
: { type: 'tag', value: tag, products, tags };
|
||||
|
||||
export const _getDoubleArgs = (name, otherVal, tags) =>
|
||||
name === 'rating' ? { type: 'tag', value: otherVal, tags } : { type: 'rating', value: otherVal };
|
||||
|
||||
/** Public */
|
||||
|
||||
/** Default value for filter selectors, saved in store */
|
||||
export const defaultFilter = { selectedCat: 0, selectedRating: 0 } as DefaultFilter;
|
||||
|
||||
/** Filters products */
|
||||
export const filterProductsBy = (type: string, products: FullProduct[], config) =>
|
||||
export const singleFilter = ({ type, value, products, tags = [] }: FilterArgs) =>
|
||||
products.filter((product) => {
|
||||
switch (type) {
|
||||
case 'tag':
|
||||
return _matchesTag(product, config);
|
||||
if (typeof value === 'number') value = value.toString();
|
||||
return _matchesTag(product, { value, $tags: tags });
|
||||
case 'rating':
|
||||
return _ratingHigher(product, config);
|
||||
return _ratingHigher(product, { value });
|
||||
}
|
||||
});
|
||||
|
||||
/** Returns either filter by one or two */
|
||||
export const multiFilter = ({ name, otherVal, rating, tag, $tags, $products }) => {
|
||||
const filterArgs = _getSingleArgs(name, rating, tag, $products, $tags);
|
||||
const filterOne = singleFilter(filterArgs);
|
||||
|
||||
if (!otherVal) return filterOne;
|
||||
|
||||
const otherFilterArgs = _getDoubleArgs(name, otherVal, $tags);
|
||||
return singleFilter({ ...otherFilterArgs, products: filterOne });
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
export * from './toProduct';
|
||||
export * from './utils/parse';
|
||||
export * from './utils/avgRating';
|
||||
export * from './param';
|
||||
export * from './avgRating';
|
||||
export * from './filters';
|
||||
export * from './param';
|
||||
export * from './parse';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { normalize, parseSlug, parseName } from '.';
|
||||
import type { UrlObject } from 'url';
|
||||
import type { Product } from '$types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
/** Gets parameter from URL of field. Defaults to 'product' */
|
||||
export const getProdParam = (field = 'product'): string =>
|
||||
|
@ -8,16 +8,47 @@ export const getProdParam = (field = 'product'): string =>
|
|||
|
||||
/** Finds a product in products store with matching name as parameter. */
|
||||
export const findProdFromParam = (
|
||||
param: URLSearchParams,
|
||||
param: string,
|
||||
$products: Product[]
|
||||
): Product | Record<string, never> =>
|
||||
$products.find(({ name }) => normalize(name) === parseSlug(param)) || {};
|
||||
|
||||
/** Resets params and navigates back to index */
|
||||
export const resetParams = () => history.pushState({}, '', new URL(window.location.origin));
|
||||
/** Reset value with empty parameters */
|
||||
export const _resetState = () => [{}, '', new URL(window.location.origin)] as const;
|
||||
|
||||
/** Updates a url with search parameters with name of product parameter. */
|
||||
export const setUrlParam = (url: URL, product: Product): UrlObject => {
|
||||
url.searchParams.set('product', parseName(product.name));
|
||||
return url;
|
||||
/** Creates a new URL and then appends parsed product to it. */
|
||||
export const _updateUrlParam = (url: string) => {
|
||||
const updatedUrl = new URL(url);
|
||||
return (product: Product) => {
|
||||
updatedUrl.searchParams.set('product', parseName(product.name));
|
||||
return updatedUrl;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks that window parameters are equal to passed product name
|
||||
*/
|
||||
export const _didWinUrlUpdate = (product: Product): boolean => {
|
||||
const params = new URLSearchParams(window.location.search).get('product');
|
||||
return parseSlug(params) === normalize(product.name);
|
||||
};
|
||||
|
||||
export const _pushParams = (url: URL) => window.history.pushState(window.history.state, '', url);
|
||||
|
||||
/** Public */
|
||||
|
||||
/** Navigates to product */
|
||||
export const goToProduct = ({ detail }) => {
|
||||
const { product, currentProduct } = detail;
|
||||
const urlWithParam = _updateUrlParam(window.location.href)(product);
|
||||
_pushParams(urlWithParam);
|
||||
|
||||
return _didWinUrlUpdate(product) ? currentProduct.set(product) : resetHistory();
|
||||
};
|
||||
|
||||
export const resetHistory = () => window.history.pushState(..._resetState());
|
||||
|
||||
export const setCurrProdFromParam = (
|
||||
currentProduct: Writable<Product | Record<string, unknown>>,
|
||||
products: Product[]
|
||||
) => currentProduct.set(findProdFromParam(getProdParam(), products));
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { Product } from '$types';
|
||||
|
||||
/** Removes casing and trims trailing/leading whitespace from string */
|
||||
export const normalize = (str: string): string => str.toLowerCase().trim();
|
||||
|
||||
|
@ -6,3 +8,7 @@ export const parseSlug = (slug: string): string => normalize(slug).replaceAll('-
|
|||
|
||||
/** Parses a name to return normalized slug with all spaces replaced with dashes */
|
||||
export const parseName = (name: string): string => normalize(name).replaceAll(' ', '-');
|
||||
|
||||
/** Product name includes value */
|
||||
export const includesName = (name: string, product: Product) =>
|
||||
normalize(product.name).includes(normalize(name));
|
|
@ -1,26 +1,46 @@
|
|||
import { filterProductsBy } from '$helpers';
|
||||
import { singleFilter } from '$helpers';
|
||||
import type { Product, Tag } from '$types';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { testProducts, testTags } from './data';
|
||||
|
||||
describe('filterProducts', () => {
|
||||
test('filters products by passed category tag', () => {
|
||||
expect(
|
||||
filterProductsBy('tag', testProducts, { value: 'Exercise', $tags: testTags })
|
||||
singleFilter({
|
||||
type: 'tag',
|
||||
products: testProducts as Product[],
|
||||
value: 'Exercise',
|
||||
tags: testTags as Tag[]
|
||||
})
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
filterProductsBy('tag', testProducts, { value: 'Electronics', $tags: testTags })
|
||||
singleFilter({
|
||||
type: 'tag',
|
||||
products: testProducts as Product[],
|
||||
value: 'Electronics',
|
||||
tags: testTags as Tag[]
|
||||
})
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('filter by tag returns unchanged products if tag does not exist', () => {
|
||||
expect(filterProductsBy('tag', testProducts, { value: 'grapes', $tags: testTags })).toEqual(
|
||||
testProducts
|
||||
);
|
||||
test('filter by tag returns nothing if tag does not exist', () => {
|
||||
expect(
|
||||
singleFilter({
|
||||
type: 'tag',
|
||||
products: testProducts as Product[],
|
||||
value: 'grapes',
|
||||
tags: testTags as Tag[]
|
||||
})
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
test('filters ratings only greater than # passed in', () => {
|
||||
expect(filterProductsBy('rating', testProducts, { value: 3 })).toHaveLength(3);
|
||||
expect(
|
||||
singleFilter({ type: 'rating', products: testProducts as Product[], value: 3 })
|
||||
).toHaveLength(3);
|
||||
|
||||
expect(filterProductsBy('rating', testProducts, { value: 5 })).toHaveLength(0);
|
||||
expect(
|
||||
singleFilter({ type: 'rating', products: testProducts as Product[], value: 5 })
|
||||
).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import { parseSlug, normalize, setUrlParam, resetParams } from '.';
|
||||
import type { Product } from '$types';
|
||||
import type { Writable} from "svelte/store"
|
||||
|
||||
|
||||
/**
|
||||
* Updates Window URL and checks that it is equal to product name
|
||||
*/
|
||||
const didWinUrlUpdate = (url: URL, product:Product): boolean => {
|
||||
window.history.pushState(window.history.state, '', url);
|
||||
const params = new URLSearchParams(window.location.search).get('product');
|
||||
return parseSlug(params) === normalize(product.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a `product` and `currentProduct` store
|
||||
* Sets URL params to parsed product name and updates Browser URL
|
||||
* Sets `currentProduct` store to `product`.
|
||||
*/
|
||||
export const toProduct = (product: Product, currentProduct:Writable<Product>) =>
|
||||
didWinUrlUpdate(setUrlParam((new URL(window.location.href)), product), product)?
|
||||
currentProduct.set(product): resetParams()
|
|
@ -45,7 +45,7 @@
|
|||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<PrevNext />
|
||||
<PrevNext on:toProduct />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import { currentProduct, productsView } from '$lib/stores';
|
||||
import { toProduct } from '$helpers';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
$: foundIndex = $productsView.findIndex((prod) => prod._id === $currentProduct._id);
|
||||
$: PREV = $productsView[foundIndex - 1];
|
||||
$: NEXT = $productsView[foundIndex + 1];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const navigate = (e) => {
|
||||
if (e.target.name === 'prev' && PREV) {
|
||||
toProduct(PREV, currentProduct);
|
||||
}
|
||||
if (e.target.name === 'next' && NEXT) {
|
||||
toProduct(NEXT, currentProduct);
|
||||
}
|
||||
let product;
|
||||
if (e.target.name === 'prev' && PREV) product = PREV;
|
||||
if (e.target.name === 'next' && NEXT) product = NEXT;
|
||||
dispatch('toProduct', {
|
||||
product,
|
||||
currentProduct
|
||||
});
|
||||
};
|
||||
|
||||
const { container, btn } = {
|
||||
|
@ -23,7 +26,7 @@
|
|||
|
||||
<div class={container}>
|
||||
{#if PREV}
|
||||
<button on:click={navigate} class={`${btn} pr-4`} name='prev'
|
||||
<button on:click={navigate} class={`${btn} pr-4`} name="prev"
|
||||
><svg focusable="false" width={50} height={50} viewBox="0 0 24 24">
|
||||
<path d="m14 7-5 5 5 5V7z" />
|
||||
</svg><span class="text-left">{PREV.name}</span></button
|
||||
|
@ -33,7 +36,7 @@
|
|||
{/if}
|
||||
|
||||
{#if NEXT}
|
||||
<button on:click={navigate} class={`${btn} pl-4`} name='next'
|
||||
<button on:click={navigate} class={`${btn} pl-4`} name="next"
|
||||
><span class="text-right">{NEXT.name}</span><svg
|
||||
focusable="false"
|
||||
width={50}
|
||||
|
@ -55,6 +58,6 @@
|
|||
|
||||
button:hover {
|
||||
background-image: url('dither.gif');
|
||||
background-repeat: repeat;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
<script>
|
||||
export let tag;
|
||||
<script lang="ts">
|
||||
import type { Ref } from '$types';
|
||||
import { tags, currentProduct, productsView, products, filters } from '$lib/stores';
|
||||
import { filterProductsBy, resetParams } from '$helpers';
|
||||
import { singleFilter, resetHistory } from '$helpers';
|
||||
|
||||
export let tag: Ref;
|
||||
const tagName = $tags.find((dirTag) => dirTag._id === tag._ref).name;
|
||||
|
||||
const filter = () => {
|
||||
currentProduct.set({});
|
||||
productsView.set(filterProductsBy('tag', $products, { value: tagName, $tags }));
|
||||
productsView.set(
|
||||
singleFilter({ type: 'tag', value: tagName, products: $products, tags: $tags })
|
||||
);
|
||||
filters.set({ ...$filters, selectedCat: tagName });
|
||||
resetParams();
|
||||
resetHistory();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
<script>
|
||||
import { urlFor } from './sanityClient';
|
||||
import { currentProduct } from '$lib/stores';
|
||||
import { toProduct } from '$helpers';
|
||||
export let products;
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const { container } = {
|
||||
container: 'flex flex-wrap mt-1 mb-1 justify-start'
|
||||
};
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<div class="container">
|
||||
{#each products as product}
|
||||
<button on:click={() => toProduct(product, currentProduct)}>
|
||||
<button
|
||||
on:click={() => {
|
||||
dispatch('toProduct', {
|
||||
product,
|
||||
currentProduct
|
||||
});
|
||||
}}
|
||||
>
|
||||
{#if product.image}
|
||||
<img src={urlFor(product.image).width(150).url()} alt={product.name} />
|
||||
{/if}
|
||||
|
@ -19,7 +24,10 @@
|
|||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.container {
|
||||
@apply flex flex-wrap mt-1 mb-1 justify-start;
|
||||
}
|
||||
img:hover {
|
||||
background-image: url('dither.gif');
|
||||
background-repeat: repeat;
|
||||
|
|
|
@ -1,48 +1,36 @@
|
|||
<script>
|
||||
import { productsView, products, tags, filters } from '$lib/stores';
|
||||
import { normalize, filterProductsBy } from '$helpers';
|
||||
import { normalize, multiFilter } from '$helpers';
|
||||
export let reset;
|
||||
let { selectedCat, selectedRating } = $filters;
|
||||
|
||||
const stars = ['⭐ ⬆', '⭐⭐ ⬆', '⭐⭐⭐ ⬆', '⭐⭐⭐⭐ ⬆', '⭐⭐⭐⭐⭐ ⬆'];
|
||||
|
||||
const filterProducts = (name, otherVal, { rating = '', tag = '', $tags = [] }) => {
|
||||
const { otherValName, filterArgs, otherFilterArgs } =
|
||||
name === 'rating'
|
||||
? {
|
||||
otherValName: 'tag',
|
||||
filterArgs: { value: rating },
|
||||
otherFilterArgs: { value: otherVal, $tags }
|
||||
}
|
||||
: {
|
||||
otherValName: 'rating',
|
||||
filterArgs: { value: tag, $tags },
|
||||
otherFilterArgs: { value: otherVal }
|
||||
};
|
||||
|
||||
const filterOne = filterProductsBy(name, $products, filterArgs);
|
||||
const filterBoth = filterProductsBy(otherValName, filterOne, otherFilterArgs);
|
||||
|
||||
return otherVal ? filterBoth : filterOne;
|
||||
};
|
||||
|
||||
const filter = ({ target: { name, value } }) => {
|
||||
if (Number(value) === 0) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
switch (normalize(name)) {
|
||||
case 'rating':
|
||||
productsView.set(
|
||||
filterProducts(name, selectedCat, { rating: value, tag: selectedCat, $tags })
|
||||
);
|
||||
break;
|
||||
case 'tag':
|
||||
productsView.set(
|
||||
filterProducts(name, selectedRating, { rating: selectedRating, tag: value, $tags })
|
||||
);
|
||||
break;
|
||||
}
|
||||
const ratingArgs = {
|
||||
name,
|
||||
otherVal: selectedCat,
|
||||
rating: value,
|
||||
tag: selectedCat,
|
||||
$tags,
|
||||
$products
|
||||
};
|
||||
const tagArgs = {
|
||||
name,
|
||||
otherVal: selectedRating,
|
||||
rating: selectedRating,
|
||||
tag: value,
|
||||
$tags,
|
||||
$products
|
||||
};
|
||||
|
||||
return normalize(name) === 'rating'
|
||||
? productsView.set(multiFilter(ratingArgs))
|
||||
: productsView.set(multiFilter(tagArgs));
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script>
|
||||
export let reset;
|
||||
const { title } = {
|
||||
title: 'font-bold p-4 pr-0 snap-start sticky top-0 bg-white h-12 shadow-sm shadow-white'
|
||||
};
|
||||
</script>
|
||||
|
||||
<a href="/" class={title} on:click={reset}> Rating Room </a>
|
||||
<a href="/" class="title" on:click={reset}> Rating Room </a>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.title {
|
||||
@apply font-bold p-4 pr-0 snap-start sticky top-0 bg-white h-12 shadow-sm shadow-white;
|
||||
}
|
||||
a:hover {
|
||||
background-image: url('dither.gif');
|
||||
background-repeat: repeat;
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
<script>
|
||||
export let productsView;
|
||||
export let currentProduct;
|
||||
import { toProduct } from '$helpers';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const { container, productStyle } = {
|
||||
container: 'pt-4',
|
||||
productList: 'flex flex-col items-start mt-10 text-sm',
|
||||
productStyle: 'w-full text-left snap-start snap-always'
|
||||
};
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<div class="container">
|
||||
{#each productsView as product}
|
||||
<button class={productStyle} on:click={() => toProduct(product, currentProduct)}>
|
||||
<button
|
||||
class="productStyle"
|
||||
on:click={() => {
|
||||
dispatch('toProduct', {
|
||||
product,
|
||||
currentProduct
|
||||
});
|
||||
}}
|
||||
>
|
||||
<p
|
||||
class={`pl-4 hover:bg-gray-200 ${
|
||||
$currentProduct && $currentProduct.name === product.name ? 'dither' : ''
|
||||
|
@ -23,3 +27,13 @@
|
|||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.container {
|
||||
@apply pt-4;
|
||||
}
|
||||
|
||||
.productStyle {
|
||||
@apply w-full text-left snap-start snap-always;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
<script>
|
||||
import { products, productsView } from '$lib/stores';
|
||||
import { normalize } from '$helpers';
|
||||
import { includesName } from '$helpers';
|
||||
|
||||
const searchProducts = (e) => {
|
||||
productsView.set(
|
||||
$products.filter((product) => normalize(product.name).includes(normalize(e.target.value)))
|
||||
);
|
||||
};
|
||||
|
||||
const { container, input } = {
|
||||
container: 'h-12 ml-auto mr-auto flex justify-center items-center',
|
||||
input: 'outline outline-1 w-28 focus:outline-blue-600 p-1 text-sm'
|
||||
productsView.set($products.filter((product) => includesName(e.target.value, product)));
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<input on:input={searchProducts} class={input} placeholder="search" />
|
||||
<div class="container">
|
||||
<input on:input={searchProducts} class="input" placeholder="search" />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.container {
|
||||
@apply h-12 ml-auto mr-auto flex justify-center items-center;
|
||||
}
|
||||
|
||||
.input {
|
||||
@apply outline outline-1 w-28 focus:outline-blue-600 p-1 text-sm;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -33,19 +33,22 @@
|
|||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const { container, sortBar, sortTitle } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6',
|
||||
sortBar: 'pl-4 p-2 flex justify-between',
|
||||
sortTitle: 'font-bold'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<div class={sortBar}>
|
||||
<div class={sortTitle}>Sort</div>
|
||||
<div class="container">
|
||||
<div class="sortBar">
|
||||
<div class="font-bold">Sort</div>
|
||||
</div>
|
||||
{#each sortOptions as option}
|
||||
<SortOption {option} {sort} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.container {
|
||||
@apply flex flex-col text-sm h-auto mb-4 mr-6;
|
||||
}
|
||||
.sortBar {
|
||||
@apply pl-4 p-2 flex justify-between;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,26 +2,28 @@
|
|||
export let option;
|
||||
export let sort;
|
||||
let current = true;
|
||||
|
||||
const { sortOption, noPointer } = {
|
||||
sortOption: 'flex p-1 pl-5 mr-12 w-full flex justify-between',
|
||||
noPointer: 'pointer-events-none'
|
||||
};
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={sortOption}
|
||||
class="sortOption"
|
||||
on:click={(e) => {
|
||||
sort(e.target.name, current);
|
||||
current = !current;
|
||||
}}
|
||||
name={option}
|
||||
>
|
||||
<p class={noPointer}>{option}</p>
|
||||
<p class={noPointer}>{current ? '▲' : '▼'}</p>
|
||||
<p class="noPointer">{option}</p>
|
||||
<p class="noPointer">{current ? '▲' : '▼'}</p>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.sortOption {
|
||||
@apply p-1 pl-5 mr-12 w-full flex justify-between;
|
||||
}
|
||||
|
||||
.noPointer {
|
||||
@apply pointer-events-none;
|
||||
}
|
||||
button:hover {
|
||||
background-image: url('dither.gif');
|
||||
background-repeat: repeat;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script>
|
||||
const back = () => window.history.back()
|
||||
</script>
|
||||
|
||||
<div class='flex flex-col w-screen text-2xl p-24 items-start'><p>This item could not be found!</p>
|
||||
<button on:click={back} class='outline outline-1 p-1 mt-2'>Go back?</button>
|
||||
</div>
|
||||
<div class="flex flex-col w-screen text-2xl p-24 items-start">
|
||||
<p>This item could not be found!</p>
|
||||
<button on:click={() => window.history.back()} class="outline outline-1 p-1 mt-2">Go back?</button
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Tag, Product, Emotion } from '$types';
|
||||
import { defaultFilter } from '$helpers';
|
||||
import { writable, type Writable, readable, type Readable } from 'svelte/store';
|
||||
import { client } from '$lib/sanityClient';
|
||||
|
||||
|
@ -20,5 +21,5 @@ export const emotions: Readable<Emotion[] | []> = readable(await fetchEmotionDat
|
|||
export const tags: Readable<Tag[] | []> = readable(await fetchTagData());
|
||||
|
||||
export const productsView: Writable<Product[]> = writable([]);
|
||||
export const currentProduct: Writable<Product | Record<string, never>> = writable({});
|
||||
export const filters = writable({ selectedCat: 0, selectedRating: 0 });
|
||||
export const currentProduct: Writable<Product | Record<string, unknown>> = writable({});
|
||||
export const filters = writable(defaultFilter);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { products, productsView, currentProduct, filters } from '$lib/stores';
|
||||
import { defaultFilter, resetParams } from '$helpers';
|
||||
import { defaultFilter, resetHistory, goToProduct } from '$helpers';
|
||||
import Products from '$lib/Layout/Products.svelte';
|
||||
import Header from '$lib/Layout/Header.svelte';
|
||||
import Search from '$lib/Layout/Search.svelte';
|
||||
|
@ -10,35 +10,38 @@
|
|||
|
||||
const reset = () => {
|
||||
productsView.set($products);
|
||||
resetParams();
|
||||
resetHistory();
|
||||
currentProduct.set({});
|
||||
filters.set(structuredClone(defaultFilter));
|
||||
};
|
||||
|
||||
const { main, container, sidebar } = {
|
||||
main: 'flex w-screen h-screen sfmono',
|
||||
container: 'flex flex-col h-screen justify-start shrink-0 overflow-auto w-40',
|
||||
sidebar: ' flex flex-col shrink-0'
|
||||
};
|
||||
</script>
|
||||
|
||||
<main class={main}>
|
||||
<div class={container}>
|
||||
<main class="main sfmono">
|
||||
<div class="container">
|
||||
<Header {reset} />
|
||||
<div class={sidebar}>
|
||||
<div class="sidebar">
|
||||
<Search />
|
||||
{#if !Object.keys($currentProduct).length}
|
||||
<Filters {reset} />
|
||||
<Sort />
|
||||
{:else}
|
||||
<Products productsView={$productsView} {currentProduct} />
|
||||
<Products productsView={$productsView} {currentProduct} on:toProduct={goToProduct} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
<style lang="postcss">
|
||||
.main {
|
||||
@apply flex w-screen h-screen;
|
||||
}
|
||||
.container {
|
||||
@apply flex flex-col h-screen justify-start shrink-0 overflow-auto w-40;
|
||||
}
|
||||
.sidebar {
|
||||
@apply flex flex-col shrink-0;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
import { browser } from '$app/env';
|
||||
import Grid from '$lib/Grid.svelte';
|
||||
import Feature from '$lib/Feature/Feature.svelte';
|
||||
import { products, productsView, tags, currentProduct, emotions } from '$lib/stores';
|
||||
import { findProdFromParam, getProdParam, resetParams } from '$helpers';
|
||||
import { products, productsView, currentProduct } from '$lib/stores';
|
||||
import { getProdParam, goToProduct, resetHistory, setCurrProdFromParam } from '$helpers';
|
||||
|
||||
productsView.set($products);
|
||||
$: productsView.set($products);
|
||||
|
||||
const load = () => {
|
||||
currentProduct.set(findProdFromParam(getProdParam(), $products));
|
||||
if (getProdParam() && JSON.stringify($currentProduct) === '{}') resetParams();
|
||||
setCurrProdFromParam(currentProduct, $products);
|
||||
if (getProdParam() && JSON.stringify($currentProduct) === '{}') resetHistory();
|
||||
};
|
||||
|
||||
if (browser) {
|
||||
|
@ -26,9 +26,9 @@
|
|||
|
||||
<div class="container">
|
||||
{#if Object.keys($currentProduct).length}
|
||||
<Feature />
|
||||
<Feature on:toProduct={goToProduct} />
|
||||
{:else}
|
||||
<Grid products={$productsView} />
|
||||
<Grid products={$productsView} on:toProduct={goToProduct} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
// import { client } from '$lib/sanityClient';
|
||||
// import type { Product, Tag, Emotion } from '$types';
|
||||
|
||||
export {};
|
||||
// type dataType = {
|
||||
// products: Product[],
|
||||
// tags: Tag[],
|
||||
// emotions: Emotion[]
|
||||
// }
|
||||
|
||||
// export async function GET() {
|
||||
// const products = await client.fetch('*[_type == "product"]');
|
||||
// const tags = await client.fetch('*[_type == "tag"]');
|
||||
// const emotions = await client.fetch('*[_type == "emotion"]');
|
||||
|
||||
// const data:dataType = {
|
||||
// products,
|
||||
// tags,
|
||||
// emotions
|
||||
// };
|
||||
|
||||
// if (data) {
|
||||
// return {
|
||||
// status: 200,
|
||||
// body: {
|
||||
// data: data
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
// return {
|
||||
// status: 404
|
||||
// };
|
||||
// }
|
|
@ -38,21 +38,6 @@ export interface Ref {
|
|||
_type: 'reference' | 'tag';
|
||||
}
|
||||
export interface Product {
|
||||
_createdAt: string;
|
||||
_id: string;
|
||||
_rev: string;
|
||||
_type: 'product';
|
||||
_updatedAt: string;
|
||||
description: string;
|
||||
image?: Image;
|
||||
name: string;
|
||||
rating?: Ref[];
|
||||
tags?: Ref[];
|
||||
url?: string;
|
||||
subname?: string;
|
||||
}
|
||||
|
||||
export interface FullProduct {
|
||||
_createdAt: string;
|
||||
_id: string;
|
||||
_rev: string;
|
||||
|
|
Loading…
Reference in New Issue