First commit

This commit is contained in:
Xinrui Chen 2022-07-10 18:16:16 -07:00
commit 7e99511f01
59 changed files with 54399 additions and 0 deletions

13
.eslintignore Normal file
View File

@ -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

20
.eslintrc.cjs Normal file
View File

@ -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
}
};

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
/backend/dist
.env
.env.*
!.env.example

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

13
.prettierignore Normal file
View File

@ -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

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

24
README.md Normal file
View File

@ -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.

3
backend/.eslintrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "@sanity/eslint-config-studio"
}

12
backend/.npmignore Normal file
View File

@ -0,0 +1,12 @@
# Logs
/logs
*.log
# Coverage directory used by tools like istanbul
/coverage
# Dependency directories
node_modules
# Compiled sanity studio
/dist

9
backend/README.md Normal file
View File

@ -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)

View File

@ -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"
}

View File

@ -0,0 +1,3 @@
{
"listOptions": {}
}

View File

@ -0,0 +1,6 @@
{
"toolSwitcher": {
"order": [],
"hidden": []
}
}

View File

@ -0,0 +1,8 @@
{
"providers": {
"mode": "append",
"redirectOnSingle": false,
"entries": []
},
"loginMethod": "dual"
}

View File

@ -0,0 +1,5 @@
{
"images": {
"directUploads": true
}
}

View File

@ -0,0 +1,3 @@
{
"defaultApiVersion": "2021-10-21"
}

32034
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
backend/package.json Normal file
View File

@ -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"
}
}

1
backend/plugins/.gitkeep Normal file
View File

@ -0,0 +1 @@
User-specific packages can be placed here

29
backend/sanity.json Normal file
View File

@ -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"
}
]
}

View File

@ -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'
}
}
};

109
backend/schemas/products.js Normal file
View File

@ -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,
]
};

20
backend/schemas/schema.js Normal file
View File

@ -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! */
]),
})

18
backend/schemas/tags.js Normal file
View File

@ -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',
}
]
}

1
backend/static/.gitkeep Normal file
View File

@ -0,0 +1 @@
Files placed here will be served by the Sanity server under the `/static`-prefix

BIN
backend/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

6
backend/tsconfig.json Normal file
View File

@ -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"]
}

10915
backend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

10501
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
package.json Normal file
View File

@ -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"
}
}

10
playwright.config.ts Normal file
View File

@ -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;

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

19
src/app.css Normal file
View File

@ -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;
}

11
src/app.d.ts vendored Normal file
View File

@ -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 {}
}

12
src/app.html Normal file
View File

@ -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>

View File

@ -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;

3
src/helpers/normalize.js Normal file
View File

@ -0,0 +1,3 @@
const normalize = (str) => str.toLowerCase().trim()
export default normalize;

25
src/lib/Feature.svelte Normal file
View File

@ -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>

34
src/lib/Grid.svelte Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

14
src/lib/sanityClient.js Normal file
View File

@ -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)
}

7
src/lib/stores.js Normal file
View File

@ -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([])

View File

@ -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>

24
src/routes/index.js Normal file
View File

@ -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
};
}

17
src/routes/index.svelte Normal file
View File

@ -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>

31
src/routes/product.svelte Normal file
View File

@ -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>

BIN
static/SF-Mono-Light.otf Executable file

Binary file not shown.

BIN
static/SF-Mono-Semibold.otf Executable file

Binary file not shown.

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

17
svelte.config.js Normal file
View File

@ -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;

7
tailwind.config.cjs Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {},
},
plugins: [],
}

6
tests/test.ts Normal file
View File

@ -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');
});

3
tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "./.svelte-kit/tsconfig.json",
}

22
vite.config.js Normal file
View File

@ -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;