First commit
This commit is contained in:
commit
7e99511f01
|
@ -0,0 +1,13 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
plugins: ['svelte3', '@typescript-eslint'],
|
||||
ignorePatterns: ['*.cjs'],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
/backend/dist
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
|
@ -0,0 +1,13 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Rating Room
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "@sanity/eslint-config-studio"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
/coverage
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Compiled sanity studio
|
||||
/dist
|
|
@ -0,0 +1,9 @@
|
|||
# Sanity Clean Content Studio
|
||||
|
||||
Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend.
|
||||
|
||||
Now you can do the following things:
|
||||
|
||||
- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
|
||||
- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
|
||||
- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!",
|
||||
"@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea",
|
||||
"@sanity/default-login": "e2ed4e51e97331c0699ba7cf9f67cbf76f1c6a5f806d6eabf8259b2bcb5f1002",
|
||||
"@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa",
|
||||
"@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6",
|
||||
"@sanity/vision": "da5b6ed712703ecd04bf4df560570c668aa95252c6bc1c41d6df1bda9b8b8f60"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"listOptions": {}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"toolSwitcher": {
|
||||
"order": [],
|
||||
"hidden": []
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"providers": {
|
||||
"mode": "append",
|
||||
"redirectOnSingle": false,
|
||||
"entries": []
|
||||
},
|
||||
"loginMethod": "dual"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"images": {
|
||||
"directUploads": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"defaultApiVersion": "2021-10-21"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "black-rail",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte)",
|
||||
"main": "package.json",
|
||||
"author": "Zane Schaffer <mail@zane.wiki>",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"start": "sanity start",
|
||||
"build": "sanity build"
|
||||
},
|
||||
"keywords": [
|
||||
"sanity"
|
||||
],
|
||||
"dependencies": {
|
||||
"@sanity/base": "^2.30.1",
|
||||
"@sanity/client": "^3.3.2",
|
||||
"@sanity/core": "^2.30.2",
|
||||
"@sanity/default-layout": "^2.30.1",
|
||||
"@sanity/default-login": "^2.30.1",
|
||||
"@sanity/desk-tool": "^2.30.1",
|
||||
"@sanity/eslint-config-studio": "^2.0.0",
|
||||
"@sanity/vision": "^2.30.1",
|
||||
"eslint": "^8.6.0",
|
||||
"prop-types": "^15.7",
|
||||
"react": "^17.0",
|
||||
"react-dom": "^17.0",
|
||||
"styled-components": "^5.2.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:zschaffer/rating-room.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.6"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
User-specific packages can be placed here
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"root": true,
|
||||
"project": {
|
||||
"name": "black-rail"
|
||||
},
|
||||
"api": {
|
||||
"projectId": "iw0i4yoz",
|
||||
"dataset": "production"
|
||||
},
|
||||
"plugins": [
|
||||
"@sanity/base",
|
||||
"@sanity/default-layout",
|
||||
"@sanity/default-login",
|
||||
"@sanity/desk-tool"
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [
|
||||
"@sanity/vision"
|
||||
]
|
||||
}
|
||||
},
|
||||
"parts": [
|
||||
{
|
||||
"name": "part:@sanity/base/schema",
|
||||
"path": "./schemas/schema"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
export default {
|
||||
title: 'Emotions',
|
||||
name: 'emotion',
|
||||
type: 'document',
|
||||
description: 'Emotions that our main characters can display',
|
||||
fields: [
|
||||
{
|
||||
title: 'Emotion name',
|
||||
name: 'name',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
name: 'description',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
title: 'Zane image',
|
||||
name: 'zane',
|
||||
type: 'image'
|
||||
},
|
||||
{
|
||||
title: 'Xinrui image',
|
||||
name: 'xyn',
|
||||
type: 'image'
|
||||
},
|
||||
],
|
||||
preview: {
|
||||
select: {
|
||||
title: 'name',
|
||||
subtitle: 'description'
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
const ratings = {
|
||||
title: 'Ratings',
|
||||
name: 'rating',
|
||||
type: 'array',
|
||||
of: [
|
||||
{
|
||||
type: 'object', fields: [
|
||||
{
|
||||
title: 'Rater Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
validation: Rule => Rule.required(),
|
||||
options: {
|
||||
list: [
|
||||
{ title: 'Xinrui', value: 'xyn' },
|
||||
{ title: 'Zane', value: 'zane' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Rating Number',
|
||||
name: 'rating',
|
||||
type: 'number',
|
||||
options: {
|
||||
list: [
|
||||
{ title: '⭐', value: 1 },
|
||||
{ title: '⭐⭐', value: 2 },
|
||||
{ title: '⭐⭐⭐', value: 3 },
|
||||
{ title: '⭐⭐⭐⭐', value: 4 },
|
||||
{ title: '⭐⭐⭐⭐⭐', value: 5 }
|
||||
]
|
||||
},
|
||||
validation: Rule => Rule.required()
|
||||
},
|
||||
{
|
||||
title: 'Comments',
|
||||
name: 'comments',
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
title: 'Rating Slug',
|
||||
name: 'slug',
|
||||
type: 'slug',
|
||||
options: {
|
||||
source: (doc, {parent}) => {
|
||||
console.log(doc, parent)
|
||||
return `${parent.name}_${parent.rating}_${doc.name}_${doc._id.slice(-6)}`}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Emotion',
|
||||
name: 'emotion',
|
||||
type: 'reference',
|
||||
to: [{type: 'emotion'}]
|
||||
}
|
||||
],
|
||||
preview: {
|
||||
select: {
|
||||
title: 'name',
|
||||
subtitle: 'slug.current'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
title: 'Products',
|
||||
name: 'product',
|
||||
type: 'document',
|
||||
description: 'List of all rated products',
|
||||
preview: {
|
||||
select: {
|
||||
title: 'name',
|
||||
subtitle: 'description'
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
title: 'Name of Product',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
validation: (Rule) => Rule.required()
|
||||
},
|
||||
{
|
||||
title: 'Upload an Image',
|
||||
name: 'image',
|
||||
type: 'image'
|
||||
},
|
||||
{
|
||||
title: 'Select Tags',
|
||||
name: 'tags',
|
||||
type: 'array',
|
||||
of: [{
|
||||
type: 'reference',
|
||||
to: [{type: 'tag'}]
|
||||
}]
|
||||
|
||||
},
|
||||
{
|
||||
title: 'Description of Product',
|
||||
name: 'description',
|
||||
type: 'text'
|
||||
},
|
||||
ratings,
|
||||
|
||||
]
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
// First, we must import the schema creator
|
||||
import createSchema from 'part:@sanity/base/schema-creator'
|
||||
|
||||
// Then import schema types from any plugins that might expose them
|
||||
import schemaTypes from 'all:part:@sanity/base/schema-type'
|
||||
|
||||
import products from './products'
|
||||
import emotions from './emotions'
|
||||
import tags from './tags'
|
||||
// Then we give our schema to the builder and provide the result to Sanity
|
||||
export default createSchema({
|
||||
// We name our schema
|
||||
name: 'default',
|
||||
// Then proceed to concatenate our document type
|
||||
// to the ones provided by any plugins that are installed
|
||||
types: schemaTypes.concat([
|
||||
products, emotions, tags
|
||||
/* Your types here! */
|
||||
]),
|
||||
})
|
|
@ -0,0 +1,18 @@
|
|||
export default {
|
||||
title: 'Tags',
|
||||
name: 'tag',
|
||||
type: 'document',
|
||||
fields: [
|
||||
{
|
||||
title: 'Tag name',
|
||||
name: 'name',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
title: 'Image',
|
||||
name: 'image',
|
||||
type: 'image',
|
||||
}
|
||||
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Files placed here will be served by the Sanity server under the `/static`-prefix
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
// Note: This config is only used to help editors like VS Code understand/resolve
|
||||
// parts, the actual transpilation is done by babel. Any compiler configuration in
|
||||
// here will be ignored.
|
||||
"include": ["./node_modules/@sanity/base/types/**/*.ts", "./**/*.ts", "./**/*.tsx"]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "rating-room",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync",
|
||||
"test": "playwright test",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
|
||||
"format": "prettier --write --plugin-search-dir=. ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.18.6",
|
||||
"@playwright/test": "^1.22.2",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
||||
"@typescript-eslint/parser": "^5.27.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.44.0",
|
||||
"svelte-check": "^2.7.1",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tailwindcss": "^3.1.5",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.2",
|
||||
"vite": "^2.9.13"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@sanity/client": "^3.3.2",
|
||||
"@sanity/image-url": "^1.0.1",
|
||||
"@sanity/types": "^2.29.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 3000
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: SF Mono;
|
||||
src: url(SF-Mono-Light.otf);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: SF Mono;
|
||||
font-weight: bold;
|
||||
src: url(SF-Mono-Semibold.otf);
|
||||
}
|
||||
|
||||
.sfmono {
|
||||
font-family: SF Mono, sans-serif;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface Platform {}
|
||||
// interface Session {}
|
||||
// interface Stuff {}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,7 @@
|
|||
const getAvgRating = (ratings) => {
|
||||
if (!ratings) return 0;
|
||||
if (ratings.length === 1) return ratings[0].rating;
|
||||
else return ratings.reduce((prev, curr) => prev.rating + curr.rating) / ratings.length;
|
||||
};
|
||||
|
||||
export default getAvgRating;
|
|
@ -0,0 +1,3 @@
|
|||
const normalize = (str) => str.toLowerCase().trim()
|
||||
|
||||
export default normalize;
|
|
@ -0,0 +1,25 @@
|
|||
<script>
|
||||
import { urlFor } from './sanityClient';
|
||||
export let product;
|
||||
export let nextProduct;
|
||||
export let prevProduct;
|
||||
</script>
|
||||
|
||||
<div class="content">
|
||||
<button on:click={prevProduct}>prev</button>
|
||||
<button on:click={nextProduct}>next</button>
|
||||
<p>{product.name}</p>
|
||||
{#if product.image}
|
||||
<img src={urlFor(product.image).url()} alt={product.name} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: calc(100% - 160px);
|
||||
margin-left: 160px;
|
||||
float: left;
|
||||
margin-top: 48px;
|
||||
padding: 0 32px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
import { urlFor } from './sanityClient';
|
||||
import { currentProduct } from '$lib/stores.js';
|
||||
export let products;
|
||||
</script>
|
||||
|
||||
<div class="content">
|
||||
{#each products as product}
|
||||
<li class="product">
|
||||
<a on:click={currentProduct.set(product)} href="/product">
|
||||
{#if product.image}
|
||||
<img src={urlFor(product.image).width(125).height(125).url()} alt={product.name} />
|
||||
{/if}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
width: calc(100% - 160px);
|
||||
margin-left: 160px;
|
||||
float: left;
|
||||
margin-top: 48px;
|
||||
padding: 0 32px;
|
||||
}
|
||||
.product {
|
||||
display: inline;
|
||||
float: left;
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
<script>
|
||||
import { productsView, products, tags } from '$lib/stores';
|
||||
import normalize from '$helpers/normalize';
|
||||
import getAvgRating from '$helpers/getAvgRating';
|
||||
export let selectedCat;
|
||||
export let selectedRating;
|
||||
export let reset;
|
||||
|
||||
const stars = ['⭐ ⬆', '⭐⭐ ⬆', '⭐⭐⭐ ⬆', '⭐⭐⭐⭐ ⬆', '⭐⭐⭐⭐⭐ ⬆'];
|
||||
|
||||
const filterByCat = (products, value) =>
|
||||
products.filter((product) => {
|
||||
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;
|
||||
});
|
||||
|
||||
const filterByRating = (products, value) =>
|
||||
products.filter((product) => product.rating && getAvgRating(product.rating) >= Number(value));
|
||||
|
||||
const filter = ({ target: { name, value } }) => {
|
||||
if (Number(value) === 0) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
switch (normalize(name)) {
|
||||
case 'rating':
|
||||
productsView.set(filterByRating($products, value));
|
||||
|
||||
break;
|
||||
case 'category':
|
||||
productsView.set(filterByCat($products, value));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const { container, filterBar, filterTitle, filterSort, option } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6',
|
||||
filterBar: 'pl-8 p-2 pr-2 flex justify-between',
|
||||
filterTitle: 'font-bold',
|
||||
filterSort: 'p-1 pl-9 mr-12 hover:bg-blue-300 w-full',
|
||||
option: 'focus:outline-none appearance-none'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<div class={filterBar}>
|
||||
<div class={filterTitle}>Filter</div>
|
||||
<button on:click={reset}> ✖️ </button>
|
||||
</div>
|
||||
|
||||
<select on:change={filter} class={filterSort} name="category" bind:value={selectedCat}>
|
||||
<option select="selected" value={0}>Category</option>
|
||||
{#each $tags as tag (tag.name)}
|
||||
<option class={option} value={tag.name}>{tag.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<select on:change={filter} class={filterSort} name="rating" bind:value={selectedRating}>
|
||||
<option select="selected" value={0}>Rating</option>
|
||||
{#each stars as starValue, i (starValue)}
|
||||
<option class={option} value={i + 1}>{starValue}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
|
@ -0,0 +1,10 @@
|
|||
<script>
|
||||
export let reset;
|
||||
const {title} = {
|
||||
title: 'hover:bg-blue-300 text-lg font-bold m-12 ml-0 pl-12',
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href="/" class={title} on:click={reset} >
|
||||
Rating Room
|
||||
</a>
|
|
@ -0,0 +1,23 @@
|
|||
<script>
|
||||
export let productsView;
|
||||
export let currentProduct;
|
||||
|
||||
const { container, productStyle, productTitle } = {
|
||||
container: 'pt-4',
|
||||
productList: 'flex flex-col items-start mt-10 text-sm',
|
||||
productStyle: 'w-full text-left',
|
||||
productTitle: 'pl-12 hover:bg-blue-300 text-sm'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<div class={container}>
|
||||
{#each productsView as product}
|
||||
<a
|
||||
class={productStyle}
|
||||
on:click={currentProduct.set(product)}
|
||||
href="/product">
|
||||
<p class={`pl-12 hover:bg-blue-300 ${$currentProduct.name === product.name? 'bg-blue-300':''} text-sm`}>{product.name}</p>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
|
@ -0,0 +1,19 @@
|
|||
<script>
|
||||
import { products, productsView } from '$lib/stores';
|
||||
import normalize from '$helpers/normalize';
|
||||
|
||||
const searchProducts = (e) => {
|
||||
productsView.set(
|
||||
$products.filter((product) => normalize(product.name).includes(normalize(e.target.value)))
|
||||
);
|
||||
};
|
||||
|
||||
const { container, input } = {
|
||||
container: 'h-16 ml-auto mr-auto',
|
||||
input: 'outline outline-1 w-36 focus:outline-blue-600 p-1 text-sm'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<input on:input={searchProducts} class={input} placeholder="search" />
|
||||
</div>
|
|
@ -0,0 +1,53 @@
|
|||
<script>
|
||||
import SortOption from './SortOption.svelte';
|
||||
import { productsView } from '$lib/stores';
|
||||
import normalize from '$helpers/normalize';
|
||||
import getAvgRating from '$helpers/getAvgRating';
|
||||
|
||||
const sortOptions = ['Rating', 'Name', 'Created'];
|
||||
|
||||
const sort = (name, current) => {
|
||||
switch (normalize(name)) {
|
||||
case 'rating':
|
||||
productsView.set(
|
||||
$productsView.sort((prev, curr) => {
|
||||
if (current) return getAvgRating(prev.rating) < getAvgRating(curr.rating) ? -1 : 1;
|
||||
else return getAvgRating(prev.rating) < getAvgRating(curr.rating) ? 1 : -1;
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'name':
|
||||
productsView.set(
|
||||
$productsView.sort((prev, curr) => {
|
||||
if (current) return prev.name < curr.name ? -1 : 1;
|
||||
else return prev.name < curr.name ? 1 : -1;
|
||||
})
|
||||
);
|
||||
break;
|
||||
case 'created':
|
||||
console.log($productsView)
|
||||
productsView.set(
|
||||
$productsView.sort((prev, curr) => {
|
||||
if (current) return new Date(prev._createdAt) < new Date(curr._createdAt) ? -1 : 1;
|
||||
else return new Date(prev._createdAt) < new Date(curr._createdAt) ? 1 : -1;
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const { container, sortBar, sortTitle } = {
|
||||
container: 'flex flex-col text-sm h-auto mb-4 mr-6',
|
||||
sortBar: 'pl-8 p-2 flex justify-between',
|
||||
sortTitle: 'font-bold'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={container}>
|
||||
<div class={sortBar}>
|
||||
<div class={sortTitle}>Sort</div>
|
||||
</div>
|
||||
{#each sortOptions as option}
|
||||
<SortOption {option} {sort} />
|
||||
{/each}
|
||||
</div>
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
<script>
|
||||
export let option;
|
||||
export let sort;
|
||||
let current=true;
|
||||
|
||||
|
||||
const { sortOption, noPointer } = {
|
||||
sortOption: 'flex p-1 pl-9 mr-12 hover:bg-blue-300 w-full flex justify-between',
|
||||
noPointer:'pointer-events-none'
|
||||
}
|
||||
</script>
|
||||
|
||||
<button class={sortOption} on:click={(e) => {sort(e.target.name, current); current=!current; }} name={option} >
|
||||
<p class={noPointer}>{option} </p>
|
||||
<p class={noPointer}>{current?'▲':'▼'}</p>
|
||||
</button>
|
|
@ -0,0 +1,14 @@
|
|||
import SanityClientConstructor from '@sanity/client'
|
||||
import ImageUrlBuilder from '@sanity/image-url'
|
||||
import {api} from '../../backend/sanity.json'
|
||||
const {projectId, dataset} = api
|
||||
export const client = SanityClientConstructor({
|
||||
projectId,
|
||||
dataset,
|
||||
apiVersion: '2022-07-07',
|
||||
useCdn: true
|
||||
})
|
||||
const builder = ImageUrlBuilder(client)
|
||||
export function urlFor(source) {
|
||||
return builder.image(source)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export const products = writable([])
|
||||
export const currentProduct = writable({})
|
||||
export const productsView = writable([])
|
||||
|
||||
export const tags = writable([])
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import '../app.css';
|
||||
import { page } from '$app/stores'
|
||||
import { products, productsView, currentProduct } from '$lib/stores';
|
||||
import Products from '$lib/Layout/Products.svelte'
|
||||
import Header from '$lib/Layout/Header.svelte'
|
||||
import Search from '$lib/Layout/Search.svelte'
|
||||
import Filters from '$lib/Layout/Filters.svelte';
|
||||
import Sort from '$lib/Layout/Sort.svelte';
|
||||
let selectedCat;
|
||||
let selectedRating;
|
||||
|
||||
const reset = () => {
|
||||
productsView.set($products);
|
||||
selectedCat = 0;
|
||||
selectedRating = 0;
|
||||
};
|
||||
|
||||
const { main, sidebar } = {
|
||||
main: 'flex w-screen h-screen sfmono',
|
||||
sidebar: 'flex flex-col justify-start h-screen overflow-auto w-52 shrink-0'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class={main}>
|
||||
<div class={sidebar}>
|
||||
<Header {reset} />
|
||||
{#if $page.url.pathname === '/'}
|
||||
<Search />
|
||||
<Filters bind:selectedCat bind:selectedRating {reset} />
|
||||
<Sort/>
|
||||
{:else}
|
||||
<Products productsView={$productsView} currentProduct={currentProduct} />
|
||||
{/if}
|
||||
</div>
|
||||
<slot />
|
||||
</main>
|
|
@ -0,0 +1,24 @@
|
|||
console.log('hello');
|
||||
import { client } from '$lib/sanityClient';
|
||||
/** @type {import('@sveltejs/kit').RequestHandler} */
|
||||
export async function get() {
|
||||
const products = await client.fetch('*[_type == "product"]');
|
||||
const tags = await client.fetch('*[_type == "tag"]');
|
||||
|
||||
const data = {
|
||||
products,tags
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
data: data
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 404
|
||||
};
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<script>
|
||||
import Grid from '$lib/Grid.svelte';
|
||||
import { products, productsView, tags } from '$lib/stores';
|
||||
export let data;
|
||||
products.set(data.products);
|
||||
tags.set(data.tags)
|
||||
|
||||
productsView.set(data.products);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Rating Room</title>
|
||||
</svelte:head>
|
||||
|
||||
<div >
|
||||
<Grid products={$productsView} />
|
||||
</div>
|
|
@ -0,0 +1,31 @@
|
|||
<script>
|
||||
import Feature from '$lib/Feature.svelte';
|
||||
import { productsView, currentProduct } from '$lib/stores';
|
||||
|
||||
let currentProductIndex = $productsView.findIndex(
|
||||
(product) => product.name == $currentProduct.name
|
||||
);
|
||||
|
||||
function nextProduct() {
|
||||
if (currentProductIndex < $productsView.length - 1) {
|
||||
currentProductIndex++;
|
||||
} else {
|
||||
currentProductIndex = 0;
|
||||
}
|
||||
currentProduct.set($productsView[currentProductIndex]);
|
||||
}
|
||||
|
||||
function prevProduct() {
|
||||
console.log(currentProductIndex, $productsView.length);
|
||||
if (currentProductIndex > 0) {
|
||||
currentProductIndex--;
|
||||
} else {
|
||||
currentProductIndex = $productsView.length - 1;
|
||||
}
|
||||
currentProduct.set($productsView[currentProductIndex]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<Feature product={$currentProduct} {prevProduct} {nextProduct} />
|
||||
</div>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,17 @@
|
|||
import adapter from '@sveltejs/adapter-auto';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: preprocess({
|
||||
postcss: true
|
||||
}),
|
||||
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('index page has expected h1', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
expect(await page.textContent('h1')).toBe('Welcome to SvelteKit');
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { resolve } from 'path';
|
||||
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const config = {
|
||||
plugins: [sveltekit()],
|
||||
resolve: {
|
||||
alias: {
|
||||
$lib: resolve('./src/lib'),
|
||||
$helpers: resolve('./src/helpers')
|
||||
}
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
allow: ['backend']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
Loading…
Reference in New Issue